fl-web-component 2.0.8 → 2.0.10
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/README.md +4 -0
- package/dist/fl-web-component.common.2.js +126 -48
- package/dist/fl-web-component.common.2.js.map +1 -1
- package/dist/fl-web-component.common.js +7366 -1277
- package/dist/fl-web-component.common.js.map +1 -1
- package/dist/fl-web-component.css +1 -1
- package/package.json +1 -1
- package/packages/components/com-graphics/component/context.js +123 -0
- package/packages/components/com-graphics/index.vue +1243 -85
- package/packages/utils/StreamLoader.js +73 -16
- package/src/utils/threejs/editor/command.js +36 -0
- package/src/utils/threejs/editor/commands/add-element-command.js +41 -0
- package/src/utils/threejs/editor/commands/add-group-command.js +10 -0
- package/src/utils/threejs/editor/commands/clone-element-command.js +100 -0
- package/src/utils/threejs/editor/commands/move-element-command.js +57 -0
- package/src/utils/threejs/editor/commands/multi-command.js +29 -0
- package/src/utils/threejs/editor/commands/remove-element-command.js +46 -0
- package/src/utils/threejs/editor/commands/reset-original-model-style-command.js +30 -0
- package/src/utils/threejs/editor/commands/set-geometry-params-command.js +41 -0
- package/src/utils/threejs/editor/commands/set-original-model-style-command.js +56 -0
- package/src/utils/threejs/editor/commands/set-position-command.js +54 -0
- package/src/utils/threejs/editor/commands/set-rotation-command.js +53 -0
- package/src/utils/threejs/editor/commands/set-scale-command.js +54 -0
- package/src/utils/threejs/editor/commands/set-value-command.js +107 -0
- package/src/utils/threejs/editor/constants.js +107 -0
- package/src/utils/threejs/editor/element-factory.js +163 -0
- package/src/utils/threejs/editor/event-bus.js +34 -0
- package/src/utils/threejs/editor/history.js +80 -0
- package/src/utils/threejs/editor/scene-command-service.js +1529 -0
- package/src/utils/threejs/editor/scene-event-bridge.js +32 -0
- package/src/utils/threejs/editor/scene-helpers.js +415 -0
- package/src/utils/threejs/measure-angle.js +22 -2
- package/src/utils/threejs/measure-area.js +23 -3
- package/src/utils/threejs/measure-distance.js +20 -1
- package/src/utils/threejs/measure-height.js +17 -1
|
@@ -19,6 +19,7 @@ export class StreamLoader {
|
|
|
19
19
|
|
|
20
20
|
// 钩子函数
|
|
21
21
|
this.renderModelData = config.renderModelData || (async () => {});
|
|
22
|
+
this.onRangeStreamComplete = config.onRangeStreamComplete;
|
|
22
23
|
// 外部提供的交互状态检查函数(可选)
|
|
23
24
|
this.externalEnsureNotInteracting = config.ensureNotInteracting;
|
|
24
25
|
this.prefixIdKey = config.prefixIdKey || 'documentId';
|
|
@@ -161,6 +162,19 @@ export class StreamLoader {
|
|
|
161
162
|
const batchPrimitives = [];
|
|
162
163
|
const batchSize = this.batchSize;
|
|
163
164
|
let batchStart = 0;
|
|
165
|
+
const streamStats = {
|
|
166
|
+
totalBatches: 0,
|
|
167
|
+
totalMeshes: 0,
|
|
168
|
+
totalPrimitives: 0,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const processStreamBatch = async (meshesToProcess, primitivesToProcess) => {
|
|
172
|
+
if (!meshesToProcess || meshesToProcess.length === 0) return;
|
|
173
|
+
await this.processBatchData(meshesToProcess, primitivesToProcess, list, range, abortSignal);
|
|
174
|
+
streamStats.totalBatches += 1;
|
|
175
|
+
streamStats.totalMeshes += meshesToProcess.length;
|
|
176
|
+
streamStats.totalPrimitives += primitivesToProcess ? primitivesToProcess.length : 0;
|
|
177
|
+
};
|
|
164
178
|
|
|
165
179
|
const ensureNotAborted = () => {
|
|
166
180
|
if (abortSignal && abortSignal.aborted) {
|
|
@@ -189,7 +203,6 @@ export class StreamLoader {
|
|
|
189
203
|
if (abortPromise) await abortPromise;
|
|
190
204
|
const interactionPromise = ensureNotInteracting();
|
|
191
205
|
if (interactionPromise) await interactionPromise;
|
|
192
|
-
|
|
193
206
|
let content;
|
|
194
207
|
try {
|
|
195
208
|
content = await reader.read();
|
|
@@ -223,7 +236,7 @@ export class StreamLoader {
|
|
|
223
236
|
const meshesToProcess = batchMeshes.slice(batchStart, batchStart + batchSize);
|
|
224
237
|
const primitivesToProcess = batchPrimitives.slice(batchStart, batchStart + batchSize);
|
|
225
238
|
batchStart += batchSize;
|
|
226
|
-
|
|
239
|
+
await processStreamBatch(meshesToProcess, primitivesToProcess);
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
if (batchMeshes.length - batchStart > 0) {
|
|
@@ -234,7 +247,7 @@ export class StreamLoader {
|
|
|
234
247
|
const meshesToProcess = batchMeshes.slice(batchStart);
|
|
235
248
|
const primitivesToProcess = batchPrimitives.slice(batchStart);
|
|
236
249
|
batchStart = batchMeshes.length;
|
|
237
|
-
|
|
250
|
+
await processStreamBatch(meshesToProcess, primitivesToProcess);
|
|
238
251
|
}
|
|
239
252
|
break;
|
|
240
253
|
}
|
|
@@ -264,7 +277,7 @@ export class StreamLoader {
|
|
|
264
277
|
const meshesToProcess = batchMeshes.slice(batchStart, batchStart + batchSize);
|
|
265
278
|
const primitivesToProcess = batchPrimitives.slice(batchStart, batchStart + batchSize);
|
|
266
279
|
batchStart += batchSize;
|
|
267
|
-
|
|
280
|
+
await processStreamBatch(meshesToProcess, primitivesToProcess);
|
|
268
281
|
}
|
|
269
282
|
|
|
270
283
|
if (batchStart >= batchSize * 4) {
|
|
@@ -278,7 +291,7 @@ export class StreamLoader {
|
|
|
278
291
|
await this.workerRequest('streamDispose', { streamId: localStreamId });
|
|
279
292
|
} catch (e) {}
|
|
280
293
|
}
|
|
281
|
-
return;
|
|
294
|
+
return streamStats;
|
|
282
295
|
}
|
|
283
296
|
|
|
284
297
|
const decoder = new TextDecoder('utf-8');
|
|
@@ -310,7 +323,7 @@ export class StreamLoader {
|
|
|
310
323
|
if (batchMeshes.length > 0) {
|
|
311
324
|
await ensureNotAborted();
|
|
312
325
|
await this.ensureNotInteracting(abortSignal);
|
|
313
|
-
await
|
|
326
|
+
await processStreamBatch(batchMeshes, batchPrimitives);
|
|
314
327
|
}
|
|
315
328
|
break;
|
|
316
329
|
}
|
|
@@ -410,7 +423,7 @@ export class StreamLoader {
|
|
|
410
423
|
if (batchMeshes.length >= this.batchSize) {
|
|
411
424
|
await ensureNotAborted();
|
|
412
425
|
await this.ensureNotInteracting(abortSignal);
|
|
413
|
-
await
|
|
426
|
+
await processStreamBatch(batchMeshes, batchPrimitives);
|
|
414
427
|
|
|
415
428
|
batchMeshes.length = 0;
|
|
416
429
|
batchPrimitives.length = 0;
|
|
@@ -423,20 +436,32 @@ export class StreamLoader {
|
|
|
423
436
|
}
|
|
424
437
|
}
|
|
425
438
|
}
|
|
439
|
+
return streamStats;
|
|
426
440
|
}
|
|
427
441
|
|
|
428
442
|
// ----------------------------------------------------------------
|
|
429
443
|
// 数据处理与渲染
|
|
430
444
|
// ----------------------------------------------------------------
|
|
431
445
|
|
|
432
|
-
processBatchData(meshes, primitives, list, range, abortSignal = null) {
|
|
446
|
+
async processBatchData(meshes, primitives, list, range, abortSignal = null) {
|
|
433
447
|
try {
|
|
434
448
|
if (abortSignal && abortSignal.aborted) {
|
|
435
449
|
throw new DOMException('Request was aborted', 'AbortError');
|
|
436
450
|
}
|
|
437
|
-
this.renderModelData(meshes, primitives, list, range
|
|
451
|
+
const renderResult = await this.renderModelData(meshes, primitives, list, range, null, undefined, {
|
|
452
|
+
suppressLoadComplete: true,
|
|
453
|
+
source: 'inRangeDis2',
|
|
454
|
+
});
|
|
455
|
+
if (renderResult && renderResult.canceled) {
|
|
456
|
+
throw new DOMException('Batch loading was canceled', 'AbortError');
|
|
457
|
+
}
|
|
458
|
+
if (abortSignal && abortSignal.aborted) {
|
|
459
|
+
throw new DOMException('Request was aborted', 'AbortError');
|
|
460
|
+
}
|
|
461
|
+
return renderResult;
|
|
438
462
|
} catch (error) {
|
|
439
463
|
console.error('Failed to render batch data:', error);
|
|
464
|
+
throw error;
|
|
440
465
|
}
|
|
441
466
|
}
|
|
442
467
|
|
|
@@ -481,10 +506,24 @@ export class StreamLoader {
|
|
|
481
506
|
this.currentStreamReader = reader;
|
|
482
507
|
this.activeStreamReaders.add(reader);
|
|
483
508
|
|
|
484
|
-
await this.parseStreamImmediate(
|
|
509
|
+
const streamStats = await this.parseStreamImmediate(
|
|
510
|
+
reader,
|
|
511
|
+
list,
|
|
512
|
+
range,
|
|
513
|
+
internalController.signal,
|
|
514
|
+
requestId
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
if (internalController.signal.aborted) {
|
|
518
|
+
throw new DOMException('Request was aborted', 'AbortError');
|
|
519
|
+
}
|
|
485
520
|
|
|
486
521
|
if (res) {
|
|
487
|
-
return
|
|
522
|
+
return {
|
|
523
|
+
stream: res,
|
|
524
|
+
requestId,
|
|
525
|
+
...(streamStats || {}),
|
|
526
|
+
};
|
|
488
527
|
}
|
|
489
528
|
return null;
|
|
490
529
|
} catch (error) {
|
|
@@ -634,7 +673,7 @@ export class StreamLoader {
|
|
|
634
673
|
// Adaptation: utils used getPrimitivesByRangeStreamWithAutoAbort which calls getPrimitivesByRangeStream
|
|
635
674
|
// Here I simplify by calling getPrimitivesByRangeStream directly but managing request key
|
|
636
675
|
|
|
637
|
-
const
|
|
676
|
+
const streamResult = await this.getPrimitivesByRangeStream(
|
|
638
677
|
range,
|
|
639
678
|
list,
|
|
640
679
|
range,
|
|
@@ -642,8 +681,8 @@ export class StreamLoader {
|
|
|
642
681
|
);
|
|
643
682
|
|
|
644
683
|
this.cleanupRequest(request.requestId);
|
|
645
|
-
if (!
|
|
646
|
-
return
|
|
684
|
+
if (!streamResult) return null;
|
|
685
|
+
return streamResult;
|
|
647
686
|
} catch (error) {
|
|
648
687
|
if (error.name === 'AbortError') {
|
|
649
688
|
// throw error;
|
|
@@ -807,7 +846,11 @@ export class StreamLoader {
|
|
|
807
846
|
async fetchJsonStream(list, range, abortSignal = null, requestId = null) {
|
|
808
847
|
try {
|
|
809
848
|
const loadStartTime = Date.now();
|
|
810
|
-
await this.fetchPrimitiveBufferByStream(range, list, abortSignal, requestId);
|
|
849
|
+
const streamResult = await this.fetchPrimitiveBufferByStream(range, list, abortSignal, requestId);
|
|
850
|
+
return {
|
|
851
|
+
...(streamResult || {}),
|
|
852
|
+
duration: Date.now() - loadStartTime,
|
|
853
|
+
};
|
|
811
854
|
} catch (err) {
|
|
812
855
|
if (err.name === 'AbortError') {
|
|
813
856
|
throw err;
|
|
@@ -1390,8 +1433,22 @@ export class StreamLoader {
|
|
|
1390
1433
|
// }
|
|
1391
1434
|
|
|
1392
1435
|
try {
|
|
1393
|
-
await this.fetchJsonStream(
|
|
1436
|
+
const streamResult = await this.fetchJsonStream(
|
|
1437
|
+
primaryItem,
|
|
1438
|
+
range,
|
|
1439
|
+
this.currentAbortController.signal,
|
|
1440
|
+
requestId
|
|
1441
|
+
);
|
|
1394
1442
|
if (this.currentRequestId === requestId) {
|
|
1443
|
+
if (typeof this.onRangeStreamComplete === 'function') {
|
|
1444
|
+
await this.onRangeStreamComplete({
|
|
1445
|
+
source: 'inRangeDis2',
|
|
1446
|
+
requestId,
|
|
1447
|
+
item: primaryItem,
|
|
1448
|
+
range,
|
|
1449
|
+
...(streamResult || {}),
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1395
1452
|
this.currentAbortController = null;
|
|
1396
1453
|
this.currentRequestId = null;
|
|
1397
1454
|
} else {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export default class Command {
|
|
2
|
+
constructor(service) {
|
|
3
|
+
this.service = service;
|
|
4
|
+
this.type = 'Command';
|
|
5
|
+
this.name = '命令';
|
|
6
|
+
this.updatable = false;
|
|
7
|
+
this.inMemory = true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
execute() {}
|
|
11
|
+
|
|
12
|
+
undo() {}
|
|
13
|
+
|
|
14
|
+
canUpdate() {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
update() {}
|
|
19
|
+
|
|
20
|
+
getObject() {
|
|
21
|
+
return this.object || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getChangedKeys() {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
toJSON() {
|
|
29
|
+
return {
|
|
30
|
+
type: this.type,
|
|
31
|
+
name: this.name,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fromJSON() {}
|
|
36
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
import { insertObjectAt, removeObjectFromParent } from '../scene-helpers';
|
|
4
|
+
|
|
5
|
+
export default class AddElementCommand extends Command {
|
|
6
|
+
constructor(service, object, options = {}) {
|
|
7
|
+
super(service);
|
|
8
|
+
this.type = COMMAND_TYPE.ADD_ELEMENT;
|
|
9
|
+
this.name = '新增元素';
|
|
10
|
+
this.object = object;
|
|
11
|
+
this.parentUuid = options.parentUuid || '';
|
|
12
|
+
this.index = options.index;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
execute() {
|
|
16
|
+
const parent = this.service.getObjectByUuid(this.parentUuid) || this.service.getCustomRoot();
|
|
17
|
+
this.parentUuid = parent.uuid;
|
|
18
|
+
insertObjectAt(parent, this.object, this.index);
|
|
19
|
+
this.index = parent.children.indexOf(this.object);
|
|
20
|
+
this.object.updateMatrixWorld(true);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
undo() {
|
|
24
|
+
removeObjectFromParent(this.object);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
toJSON() {
|
|
28
|
+
return {
|
|
29
|
+
...super.toJSON(),
|
|
30
|
+
object: this.object.toJSON(),
|
|
31
|
+
parentUuid: this.parentUuid,
|
|
32
|
+
index: this.index,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fromJSON(json) {
|
|
37
|
+
this.parentUuid = json.parentUuid;
|
|
38
|
+
this.index = json.index;
|
|
39
|
+
this.object = this.service.parseObjectJSON(json.object);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import AddElementCommand from './add-element-command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
|
|
4
|
+
export default class AddGroupCommand extends AddElementCommand {
|
|
5
|
+
constructor(service, object, options = {}) {
|
|
6
|
+
super(service, object, options);
|
|
7
|
+
this.type = COMMAND_TYPE.ADD_GROUP;
|
|
8
|
+
this.name = '新增组';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
import { cloneCustomObject } from '../element-factory';
|
|
4
|
+
import { insertObjectAt, removeObjectFromParent } from '../scene-helpers';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CLONE_OFFSET_MIN = 1;
|
|
7
|
+
const DEFAULT_CLONE_OFFSET_RATIO = 0.2;
|
|
8
|
+
|
|
9
|
+
function normalizeOffsetArray(offset = []) {
|
|
10
|
+
if (!Array.isArray(offset) || offset.length !== 3) return null;
|
|
11
|
+
return offset.map(value => Number(value) || 0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function createWorldOffsetVector(THREE, source) {
|
|
15
|
+
const bounds = new THREE.Box3();
|
|
16
|
+
const size = new THREE.Vector3();
|
|
17
|
+
source.updateMatrixWorld(true);
|
|
18
|
+
bounds.setFromObject(source);
|
|
19
|
+
bounds.getSize(size);
|
|
20
|
+
const baseOffset = Math.max(
|
|
21
|
+
Math.max(size.x || 0, size.y || 0, size.z || 0) * DEFAULT_CLONE_OFFSET_RATIO,
|
|
22
|
+
DEFAULT_CLONE_OFFSET_MIN
|
|
23
|
+
);
|
|
24
|
+
return new THREE.Vector3(baseOffset, 0, baseOffset);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function convertWorldOffsetToLocal(THREE, parent, worldOffset) {
|
|
28
|
+
if (!parent) return worldOffset.clone();
|
|
29
|
+
parent.updateMatrixWorld(true);
|
|
30
|
+
const worldOrigin = parent.getWorldPosition(new THREE.Vector3());
|
|
31
|
+
const localOrigin = parent.worldToLocal(worldOrigin.clone());
|
|
32
|
+
const localTarget = parent.worldToLocal(worldOrigin.clone().add(worldOffset));
|
|
33
|
+
return localTarget.sub(localOrigin);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolveCloneOffset(THREE, source, targetParent, options = {}) {
|
|
37
|
+
const normalizedOffset = normalizeOffsetArray(options.positionOffset);
|
|
38
|
+
if (normalizedOffset) {
|
|
39
|
+
return new THREE.Vector3().fromArray(normalizedOffset);
|
|
40
|
+
}
|
|
41
|
+
if (options.disableAutoOffset === true || !THREE || !source) {
|
|
42
|
+
return new THREE.Vector3(0, 0, 0);
|
|
43
|
+
}
|
|
44
|
+
return convertWorldOffsetToLocal(THREE, targetParent, createWorldOffsetVector(THREE, source));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default class CloneElementCommand extends Command {
|
|
48
|
+
constructor(service, uuid, options = {}) {
|
|
49
|
+
super(service);
|
|
50
|
+
this.type = COMMAND_TYPE.CLONE_ELEMENT;
|
|
51
|
+
this.name = '复制元素';
|
|
52
|
+
this.source = service.getObjectByUuid(uuid);
|
|
53
|
+
const targetParent =
|
|
54
|
+
service.getObjectByUuid(options.parentUuid) ||
|
|
55
|
+
(this.source && this.source.parent) ||
|
|
56
|
+
service.getCustomRoot();
|
|
57
|
+
this.parentUuid = targetParent.uuid;
|
|
58
|
+
this.index = options.index;
|
|
59
|
+
this.object = this.source ? cloneCustomObject(service.THREE, this.source, targetParent) : null;
|
|
60
|
+
this.positionOffset = resolveCloneOffset(service.THREE, this.source, targetParent, options);
|
|
61
|
+
if (this.object) {
|
|
62
|
+
// 复制后默认沿平面做一次可见偏移,避免与原对象完全重合。
|
|
63
|
+
this.object.position.add(this.positionOffset);
|
|
64
|
+
this.object.updateMatrixWorld(true);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
execute() {
|
|
69
|
+
if (!this.object) return;
|
|
70
|
+
const parent = this.service.getObjectByUuid(this.parentUuid) || this.service.getCustomRoot();
|
|
71
|
+
insertObjectAt(parent, this.object, this.index);
|
|
72
|
+
this.index = parent.children.indexOf(this.object);
|
|
73
|
+
this.object.updateMatrixWorld(true);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
undo() {
|
|
77
|
+
if (!this.object) return;
|
|
78
|
+
removeObjectFromParent(this.object);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
toJSON() {
|
|
82
|
+
return {
|
|
83
|
+
...super.toJSON(),
|
|
84
|
+
sourceUuid: this.source ? this.source.uuid : '',
|
|
85
|
+
parentUuid: this.parentUuid,
|
|
86
|
+
index: this.index,
|
|
87
|
+
positionOffset: this.positionOffset ? this.positionOffset.toArray() : [0, 0, 0],
|
|
88
|
+
object: this.object ? this.object.toJSON() : null,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fromJSON(json) {
|
|
93
|
+
this.parentUuid = json.parentUuid;
|
|
94
|
+
this.index = json.index;
|
|
95
|
+
this.positionOffset = Array.isArray(json.positionOffset)
|
|
96
|
+
? new this.service.THREE.Vector3().fromArray(json.positionOffset)
|
|
97
|
+
: new this.service.THREE.Vector3(0, 0, 0);
|
|
98
|
+
this.object = json.object ? this.service.parseObjectJSON(json.object) : null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
import { getObjectIndex, insertObjectAt } from '../scene-helpers';
|
|
4
|
+
|
|
5
|
+
export default class MoveElementCommand extends Command {
|
|
6
|
+
constructor(service, uuid, options = {}) {
|
|
7
|
+
super(service);
|
|
8
|
+
this.type = COMMAND_TYPE.MOVE_ELEMENT;
|
|
9
|
+
this.name = '移动元素';
|
|
10
|
+
this.object = service.getObjectByUuid(uuid);
|
|
11
|
+
this.oldParentUuid = this.object && this.object.parent ? this.object.parent.uuid : '';
|
|
12
|
+
this.oldIndex = getObjectIndex(this.object);
|
|
13
|
+
this.newParentUuid = options.targetParentUuid || '';
|
|
14
|
+
this.newIndex = options.index;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
execute() {
|
|
18
|
+
if (!this.object) return;
|
|
19
|
+
const targetParent =
|
|
20
|
+
this.service.getObjectByUuid(this.newParentUuid) || this.service.getCustomRoot();
|
|
21
|
+
insertObjectAt(targetParent, this.object, this.newIndex);
|
|
22
|
+
this.newParentUuid = targetParent.uuid;
|
|
23
|
+
this.newIndex = targetParent.children.indexOf(this.object);
|
|
24
|
+
this.object.updateMatrixWorld(true);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
undo() {
|
|
28
|
+
if (!this.object) return;
|
|
29
|
+
const oldParent =
|
|
30
|
+
this.service.getObjectByUuid(this.oldParentUuid) || this.service.getCustomRoot();
|
|
31
|
+
insertObjectAt(oldParent, this.object, this.oldIndex);
|
|
32
|
+
this.object.updateMatrixWorld(true);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getChangedKeys() {
|
|
36
|
+
return ['parentUuid'];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
toJSON() {
|
|
40
|
+
return {
|
|
41
|
+
...super.toJSON(),
|
|
42
|
+
uuid: this.object ? this.object.uuid : '',
|
|
43
|
+
oldParentUuid: this.oldParentUuid,
|
|
44
|
+
oldIndex: this.oldIndex,
|
|
45
|
+
newParentUuid: this.newParentUuid,
|
|
46
|
+
newIndex: this.newIndex,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fromJSON(json) {
|
|
51
|
+
this.object = this.service.getObjectByUuid(json.uuid);
|
|
52
|
+
this.oldParentUuid = json.oldParentUuid;
|
|
53
|
+
this.oldIndex = json.oldIndex;
|
|
54
|
+
this.newParentUuid = json.newParentUuid;
|
|
55
|
+
this.newIndex = json.newIndex;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
|
|
4
|
+
export default class MultiCommand extends Command {
|
|
5
|
+
constructor(service, commands = [], options = {}) {
|
|
6
|
+
super(service);
|
|
7
|
+
this.type = COMMAND_TYPE.MULTI;
|
|
8
|
+
this.name = options.name || '批量修改';
|
|
9
|
+
this.commands = commands;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
execute() {
|
|
13
|
+
this.commands.forEach(command => {
|
|
14
|
+
command.execute();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
undo() {
|
|
19
|
+
for (let index = this.commands.length - 1; index >= 0; index -= 1) {
|
|
20
|
+
this.commands[index].undo();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getChangedKeys() {
|
|
25
|
+
return this.commands.reduce((result, command) => {
|
|
26
|
+
return result.concat(command.getChangedKeys());
|
|
27
|
+
}, []);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
import { getObjectIndex, insertObjectAt, removeObjectFromParent } from '../scene-helpers';
|
|
4
|
+
|
|
5
|
+
export default class RemoveElementCommand extends Command {
|
|
6
|
+
constructor(service, uuid) {
|
|
7
|
+
super(service);
|
|
8
|
+
this.type = COMMAND_TYPE.REMOVE_ELEMENT;
|
|
9
|
+
this.name = '删除元素';
|
|
10
|
+
this.object = service.getObjectByUuid(uuid);
|
|
11
|
+
this.parentUuid = this.object && this.object.parent ? this.object.parent.uuid : '';
|
|
12
|
+
this.index = getObjectIndex(this.object);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
execute() {
|
|
16
|
+
if (!this.object) return;
|
|
17
|
+
if (this.object.parent) {
|
|
18
|
+
this.parentUuid = this.object.parent.uuid;
|
|
19
|
+
this.index = getObjectIndex(this.object);
|
|
20
|
+
}
|
|
21
|
+
removeObjectFromParent(this.object);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
undo() {
|
|
25
|
+
if (!this.object) return;
|
|
26
|
+
const parent = this.service.getObjectByUuid(this.parentUuid) || this.service.getCustomRoot();
|
|
27
|
+
insertObjectAt(parent, this.object, this.index);
|
|
28
|
+
this.object.updateMatrixWorld(true);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
toJSON() {
|
|
32
|
+
return {
|
|
33
|
+
...super.toJSON(),
|
|
34
|
+
uuid: this.object ? this.object.uuid : '',
|
|
35
|
+
parentUuid: this.parentUuid,
|
|
36
|
+
index: this.index,
|
|
37
|
+
object: this.object ? this.object.toJSON() : null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fromJSON(json) {
|
|
42
|
+
this.parentUuid = json.parentUuid;
|
|
43
|
+
this.index = json.index;
|
|
44
|
+
this.object = json.object ? this.service.parseObjectJSON(json.object) : null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
|
|
4
|
+
export default class ResetOriginalModelStyleCommand extends Command {
|
|
5
|
+
constructor(service, uuid) {
|
|
6
|
+
super(service);
|
|
7
|
+
this.type = COMMAND_TYPE.RESET_ORIGINAL_MODEL_STYLE;
|
|
8
|
+
this.name = '还原原始模型样式';
|
|
9
|
+
this.object = service.getObjectByUuid(uuid);
|
|
10
|
+
this.uuid = uuid;
|
|
11
|
+
this.oldStyle = service.getOriginalModelStyleValue(uuid);
|
|
12
|
+
this.resetStyle = service.ensureOriginalModelBaseline(uuid);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
execute() {
|
|
16
|
+
if (!this.object) return;
|
|
17
|
+
this.service.applyOriginalModelStyle(this.uuid, this.resetStyle);
|
|
18
|
+
this.service.syncOriginalModelStyleRecord(this.uuid);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
undo() {
|
|
22
|
+
if (!this.object) return;
|
|
23
|
+
this.service.applyOriginalModelStyle(this.uuid, this.oldStyle);
|
|
24
|
+
this.service.syncOriginalModelStyleRecord(this.uuid);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getChangedKeys() {
|
|
28
|
+
return ['color', 'opacity', 'visible'];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
import { createGeometry, normalizeGeometryParams } from '../element-factory';
|
|
4
|
+
|
|
5
|
+
export default class SetGeometryParamsCommand extends Command {
|
|
6
|
+
constructor(service, uuid, geometryParams) {
|
|
7
|
+
super(service);
|
|
8
|
+
this.type = COMMAND_TYPE.SET_GEOMETRY_PARAMS;
|
|
9
|
+
this.name = '修改几何参数';
|
|
10
|
+
this.object = service.getObjectByUuid(uuid);
|
|
11
|
+
this.elementType = this.object && this.object.userData ? this.object.userData.elementType : '';
|
|
12
|
+
this.oldParams =
|
|
13
|
+
this.object && this.object.userData && this.object.userData.geometryParams
|
|
14
|
+
? normalizeGeometryParams(this.elementType, this.object.userData.geometryParams)
|
|
15
|
+
: {};
|
|
16
|
+
this.newParams = normalizeGeometryParams(this.elementType, geometryParams);
|
|
17
|
+
this.oldGeometry = this.object ? this.object.geometry : null;
|
|
18
|
+
this.newGeometry = null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
execute() {
|
|
22
|
+
if (!this.object) return;
|
|
23
|
+
if (!this.newGeometry) {
|
|
24
|
+
this.newGeometry = createGeometry(this.service.THREE, this.elementType, this.newParams);
|
|
25
|
+
}
|
|
26
|
+
this.object.geometry = this.newGeometry;
|
|
27
|
+
this.object.userData.geometryParams = { ...this.newParams };
|
|
28
|
+
this.object.updateMatrixWorld(true);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
undo() {
|
|
32
|
+
if (!this.object) return;
|
|
33
|
+
this.object.geometry = this.oldGeometry;
|
|
34
|
+
this.object.userData.geometryParams = { ...this.oldParams };
|
|
35
|
+
this.object.updateMatrixWorld(true);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getChangedKeys() {
|
|
39
|
+
return ['geometryParams'];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
|
|
4
|
+
function cloneStyle(style = {}) {
|
|
5
|
+
return JSON.parse(JSON.stringify(style || {}));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default class SetOriginalModelStyleCommand extends Command {
|
|
9
|
+
constructor(service, uuid, style = {}) {
|
|
10
|
+
super(service);
|
|
11
|
+
this.type = COMMAND_TYPE.SET_ORIGINAL_MODEL_STYLE;
|
|
12
|
+
this.name = '修改原始模型样式';
|
|
13
|
+
this.updatable = true;
|
|
14
|
+
this.object = service.getObjectByUuid(uuid);
|
|
15
|
+
this.uuid = uuid;
|
|
16
|
+
// 首次修改前必须先固化原始样式基线,否则“还原”会回到已修改后的状态。
|
|
17
|
+
service.ensureOriginalModelBaseline(uuid);
|
|
18
|
+
this.oldStyle = service.getOriginalModelStyleValue(uuid);
|
|
19
|
+
this.newStyle = service.mergeOriginalModelStyle(this.oldStyle, style);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
execute() {
|
|
23
|
+
if (!this.object) return;
|
|
24
|
+
this.service.applyOriginalModelStyle(this.uuid, this.newStyle);
|
|
25
|
+
this.service.syncOriginalModelStyleRecord(this.uuid);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
undo() {
|
|
29
|
+
if (!this.object) return;
|
|
30
|
+
this.service.applyOriginalModelStyle(this.uuid, this.oldStyle);
|
|
31
|
+
this.service.syncOriginalModelStyleRecord(this.uuid);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
canUpdate(command, deltaTime, mergeWindow) {
|
|
35
|
+
return (
|
|
36
|
+
command &&
|
|
37
|
+
command.type === this.type &&
|
|
38
|
+
command.uuid === this.uuid &&
|
|
39
|
+
deltaTime <= mergeWindow
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
update(command) {
|
|
44
|
+
this.newStyle = cloneStyle(command.newStyle);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getChangedKeys() {
|
|
48
|
+
const changedKeys = [];
|
|
49
|
+
['color', 'opacity', 'visible'].forEach(key => {
|
|
50
|
+
if (this.oldStyle[key] !== this.newStyle[key]) {
|
|
51
|
+
changedKeys.push(key);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return changedKeys;
|
|
55
|
+
}
|
|
56
|
+
}
|