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.
- package/README.md +2 -0
- package/dist/fl-web-component.common.js +7198 -1250
- 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 +1148 -69
- 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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
|
|
4
|
+
export default class SetPositionCommand extends Command {
|
|
5
|
+
constructor(service, uuid, newPosition, oldPosition) {
|
|
6
|
+
super(service);
|
|
7
|
+
this.type = COMMAND_TYPE.SET_POSITION;
|
|
8
|
+
this.name = '修改位置';
|
|
9
|
+
this.updatable = true;
|
|
10
|
+
this.object = service.getObjectByUuid(uuid);
|
|
11
|
+
this.oldPosition =
|
|
12
|
+
oldPosition && oldPosition.length === 3
|
|
13
|
+
? new service.THREE.Vector3(oldPosition[0], oldPosition[1], oldPosition[2])
|
|
14
|
+
: this.object
|
|
15
|
+
? this.object.position.clone()
|
|
16
|
+
: new service.THREE.Vector3();
|
|
17
|
+
this.newPosition = new service.THREE.Vector3(
|
|
18
|
+
Number(newPosition[0]) || 0,
|
|
19
|
+
Number(newPosition[1]) || 0,
|
|
20
|
+
Number(newPosition[2]) || 0
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
execute() {
|
|
25
|
+
if (!this.object) return;
|
|
26
|
+
this.object.position.copy(this.newPosition);
|
|
27
|
+
this.object.updateMatrixWorld(true);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
undo() {
|
|
31
|
+
if (!this.object) return;
|
|
32
|
+
this.object.position.copy(this.oldPosition);
|
|
33
|
+
this.object.updateMatrixWorld(true);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
canUpdate(command, deltaTime, mergeWindow) {
|
|
37
|
+
return (
|
|
38
|
+
command &&
|
|
39
|
+
command.type === this.type &&
|
|
40
|
+
this.object &&
|
|
41
|
+
command.object &&
|
|
42
|
+
command.object.uuid === this.object.uuid &&
|
|
43
|
+
deltaTime <= mergeWindow
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
update(command) {
|
|
48
|
+
this.newPosition.copy(command.newPosition);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getChangedKeys() {
|
|
52
|
+
return ['position'];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
import { degreesArrayToRadians } from '../scene-helpers';
|
|
4
|
+
|
|
5
|
+
export default class SetRotationCommand extends Command {
|
|
6
|
+
constructor(service, uuid, newRotation, oldRotation) {
|
|
7
|
+
super(service);
|
|
8
|
+
this.type = COMMAND_TYPE.SET_ROTATION;
|
|
9
|
+
this.name = '修改旋转';
|
|
10
|
+
this.updatable = true;
|
|
11
|
+
this.object = service.getObjectByUuid(uuid);
|
|
12
|
+
const oldRadians = Array.isArray(oldRotation) ? degreesArrayToRadians(oldRotation) : null;
|
|
13
|
+
this.oldRotation =
|
|
14
|
+
oldRadians && oldRadians.length === 3
|
|
15
|
+
? new service.THREE.Euler(oldRadians[0], oldRadians[1], oldRadians[2], 'XYZ')
|
|
16
|
+
: this.object
|
|
17
|
+
? this.object.rotation.clone()
|
|
18
|
+
: new service.THREE.Euler();
|
|
19
|
+
const newRadians = degreesArrayToRadians(newRotation);
|
|
20
|
+
this.newRotation = new service.THREE.Euler(newRadians[0], newRadians[1], newRadians[2], 'XYZ');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
execute() {
|
|
24
|
+
if (!this.object) return;
|
|
25
|
+
this.object.rotation.copy(this.newRotation);
|
|
26
|
+
this.object.updateMatrixWorld(true);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
undo() {
|
|
30
|
+
if (!this.object) return;
|
|
31
|
+
this.object.rotation.copy(this.oldRotation);
|
|
32
|
+
this.object.updateMatrixWorld(true);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
canUpdate(command, deltaTime, mergeWindow) {
|
|
36
|
+
return (
|
|
37
|
+
command &&
|
|
38
|
+
command.type === this.type &&
|
|
39
|
+
this.object &&
|
|
40
|
+
command.object &&
|
|
41
|
+
command.object.uuid === this.object.uuid &&
|
|
42
|
+
deltaTime <= mergeWindow
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
update(command) {
|
|
47
|
+
this.newRotation.copy(command.newRotation);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getChangedKeys() {
|
|
51
|
+
return ['rotation'];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
|
|
4
|
+
export default class SetScaleCommand extends Command {
|
|
5
|
+
constructor(service, uuid, newScale, oldScale) {
|
|
6
|
+
super(service);
|
|
7
|
+
this.type = COMMAND_TYPE.SET_SCALE;
|
|
8
|
+
this.name = '修改缩放';
|
|
9
|
+
this.updatable = true;
|
|
10
|
+
this.object = service.getObjectByUuid(uuid);
|
|
11
|
+
this.oldScale =
|
|
12
|
+
oldScale && oldScale.length === 3
|
|
13
|
+
? new service.THREE.Vector3(oldScale[0], oldScale[1], oldScale[2])
|
|
14
|
+
: this.object
|
|
15
|
+
? this.object.scale.clone()
|
|
16
|
+
: new service.THREE.Vector3(1, 1, 1);
|
|
17
|
+
this.newScale = new service.THREE.Vector3(
|
|
18
|
+
Number(newScale[0]) || 1,
|
|
19
|
+
Number(newScale[1]) || 1,
|
|
20
|
+
Number(newScale[2]) || 1
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
execute() {
|
|
25
|
+
if (!this.object) return;
|
|
26
|
+
this.object.scale.copy(this.newScale);
|
|
27
|
+
this.object.updateMatrixWorld(true);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
undo() {
|
|
31
|
+
if (!this.object) return;
|
|
32
|
+
this.object.scale.copy(this.oldScale);
|
|
33
|
+
this.object.updateMatrixWorld(true);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
canUpdate(command, deltaTime, mergeWindow) {
|
|
37
|
+
return (
|
|
38
|
+
command &&
|
|
39
|
+
command.type === this.type &&
|
|
40
|
+
this.object &&
|
|
41
|
+
command.object &&
|
|
42
|
+
command.object.uuid === this.object.uuid &&
|
|
43
|
+
deltaTime <= mergeWindow
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
update(command) {
|
|
48
|
+
this.newScale.copy(command.newScale);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getChangedKeys() {
|
|
52
|
+
return ['scale'];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import Command from '../command';
|
|
2
|
+
import { COMMAND_TYPE } from '../constants';
|
|
3
|
+
import { updateDisplayName } from '../scene-helpers';
|
|
4
|
+
|
|
5
|
+
function getTargetInfo(object, propertyPath) {
|
|
6
|
+
const keys = propertyPath.split('.');
|
|
7
|
+
const propertyName = keys.pop();
|
|
8
|
+
let target = object;
|
|
9
|
+
keys.forEach(key => {
|
|
10
|
+
if (!target) return;
|
|
11
|
+
target = target[key];
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
target,
|
|
15
|
+
propertyName,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function cloneValue(value) {
|
|
20
|
+
if (value === undefined || value === null) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
if (value && value.isColor) {
|
|
24
|
+
return value.clone();
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === 'object') {
|
|
27
|
+
return JSON.parse(JSON.stringify(value));
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function assignValue(target, propertyName, value) {
|
|
33
|
+
if (!target) return;
|
|
34
|
+
if (target[propertyName] && target[propertyName].isColor) {
|
|
35
|
+
target[propertyName].set(value);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
target[propertyName] = value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default class SetValueCommand extends Command {
|
|
42
|
+
constructor(service, uuid, propertyPath, newValue) {
|
|
43
|
+
super(service);
|
|
44
|
+
this.type = COMMAND_TYPE.SET_VALUE;
|
|
45
|
+
this.name = '修改属性';
|
|
46
|
+
this.updatable = true;
|
|
47
|
+
this.object = service.getObjectByUuid(uuid);
|
|
48
|
+
this.propertyPath = propertyPath;
|
|
49
|
+
const { target, propertyName } = getTargetInfo(this.object, propertyPath);
|
|
50
|
+
this.oldValue = target ? cloneValue(target[propertyName]) : undefined;
|
|
51
|
+
this.newValue = cloneValue(newValue);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
execute() {
|
|
55
|
+
if (!this.object) return;
|
|
56
|
+
const { target, propertyName } = getTargetInfo(this.object, this.propertyPath);
|
|
57
|
+
assignValue(target, propertyName, this.newValue);
|
|
58
|
+
if (this.propertyPath === 'name') {
|
|
59
|
+
updateDisplayName(this.object);
|
|
60
|
+
}
|
|
61
|
+
if (this.propertyPath === 'material.opacity' && this.object.material) {
|
|
62
|
+
this.object.material.transparent = Number(this.object.material.opacity) < 1;
|
|
63
|
+
this.object.material.needsUpdate = true;
|
|
64
|
+
}
|
|
65
|
+
if (this.propertyPath.indexOf('material.') === 0 && this.object.material) {
|
|
66
|
+
this.object.material.needsUpdate = true;
|
|
67
|
+
}
|
|
68
|
+
this.object.updateMatrixWorld(true);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
undo() {
|
|
72
|
+
if (!this.object) return;
|
|
73
|
+
const { target, propertyName } = getTargetInfo(this.object, this.propertyPath);
|
|
74
|
+
assignValue(target, propertyName, this.oldValue);
|
|
75
|
+
if (this.propertyPath === 'name') {
|
|
76
|
+
updateDisplayName(this.object);
|
|
77
|
+
}
|
|
78
|
+
if (this.propertyPath === 'material.opacity' && this.object.material) {
|
|
79
|
+
this.object.material.transparent = Number(this.object.material.opacity) < 1;
|
|
80
|
+
this.object.material.needsUpdate = true;
|
|
81
|
+
}
|
|
82
|
+
if (this.propertyPath.indexOf('material.') === 0 && this.object.material) {
|
|
83
|
+
this.object.material.needsUpdate = true;
|
|
84
|
+
}
|
|
85
|
+
this.object.updateMatrixWorld(true);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
canUpdate(command, deltaTime, mergeWindow) {
|
|
89
|
+
return (
|
|
90
|
+
command &&
|
|
91
|
+
command.type === this.type &&
|
|
92
|
+
this.object &&
|
|
93
|
+
command.object &&
|
|
94
|
+
command.object.uuid === this.object.uuid &&
|
|
95
|
+
command.propertyPath === this.propertyPath &&
|
|
96
|
+
deltaTime <= mergeWindow
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
update(command) {
|
|
101
|
+
this.newValue = cloneValue(command.newValue);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getChangedKeys() {
|
|
105
|
+
return [this.propertyPath];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export const CUSTOM_ROOT_NAME = 'custom-model-root';
|
|
2
|
+
export const CUSTOM_ROOT_DISPLAY_NAME = '自定义模型元素';
|
|
3
|
+
export const DEFAULT_ELEMENT_COLOR = '#2C8CF0';
|
|
4
|
+
export const HISTORY_MERGE_WINDOW = 500;
|
|
5
|
+
|
|
6
|
+
export const ELEMENT_TYPES = [
|
|
7
|
+
'box',
|
|
8
|
+
'tetrahedron',
|
|
9
|
+
'octahedron',
|
|
10
|
+
'cylinder',
|
|
11
|
+
'ring',
|
|
12
|
+
'sphere',
|
|
13
|
+
'capsule',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export const ELEMENT_LABELS = {
|
|
17
|
+
box: '立方体',
|
|
18
|
+
tetrahedron: '四面体',
|
|
19
|
+
octahedron: '八面体',
|
|
20
|
+
cylinder: '圆柱体',
|
|
21
|
+
ring: '圆环体',
|
|
22
|
+
sphere: '球体',
|
|
23
|
+
capsule: '胶囊',
|
|
24
|
+
group: '组',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const TRANSFORM_MODE = {
|
|
28
|
+
TRANSLATE: 'translate',
|
|
29
|
+
ROTATE: 'rotate',
|
|
30
|
+
SCALE: 'scale',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const SCENE_NODE_TYPE = {
|
|
34
|
+
CUSTOM_ELEMENT: 'custom-element',
|
|
35
|
+
CUSTOM_GROUP: 'custom-group',
|
|
36
|
+
CUSTOM_ROOT: 'custom-root',
|
|
37
|
+
ORIGINAL_MODEL: 'original-model',
|
|
38
|
+
HELPER: 'helper',
|
|
39
|
+
UNKNOWN: 'unknown',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const EDITOR_EVENT = {
|
|
43
|
+
OBJECT_ADDED: 'objectAdded',
|
|
44
|
+
OBJECT_REMOVED: 'objectRemoved',
|
|
45
|
+
OBJECT_CHANGED: 'objectChanged',
|
|
46
|
+
OBJECT_SELECTED: 'objectSelected',
|
|
47
|
+
OBJECT_TRANSFORM_CHANGING: 'objectTransformChanging',
|
|
48
|
+
OBJECT_TRANSFORM_CHANGED: 'objectTransformChanged',
|
|
49
|
+
SCENE_GRAPH_CHANGED: 'sceneGraphChanged',
|
|
50
|
+
HISTORY_CHANGED: 'historyChanged',
|
|
51
|
+
TRANSFORM_MODE_CHANGED: 'transformModeChanged',
|
|
52
|
+
ORIGINAL_MODEL_SELECTED: 'originalModelSelected',
|
|
53
|
+
ORIGINAL_MODEL_STYLE_CHANGED: 'originalModelStyleChanged',
|
|
54
|
+
ORIGINAL_MODEL_STYLE_RESET: 'originalModelStyleReset',
|
|
55
|
+
ORIGINAL_MODEL_CHANGE_LIST_CHANGED: 'originalModelChangeListChanged',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const COMMAND_TYPE = {
|
|
59
|
+
ADD_ELEMENT: 'AddElementCommand',
|
|
60
|
+
ADD_GROUP: 'AddGroupCommand',
|
|
61
|
+
REMOVE_ELEMENT: 'RemoveElementCommand',
|
|
62
|
+
CLONE_ELEMENT: 'CloneElementCommand',
|
|
63
|
+
MOVE_ELEMENT: 'MoveElementCommand',
|
|
64
|
+
SET_POSITION: 'SetPositionCommand',
|
|
65
|
+
SET_ROTATION: 'SetRotationCommand',
|
|
66
|
+
SET_SCALE: 'SetScaleCommand',
|
|
67
|
+
SET_VALUE: 'SetValueCommand',
|
|
68
|
+
SET_GEOMETRY_PARAMS: 'SetGeometryParamsCommand',
|
|
69
|
+
SET_ORIGINAL_MODEL_STYLE: 'SetOriginalModelStyleCommand',
|
|
70
|
+
RESET_ORIGINAL_MODEL_STYLE: 'ResetOriginalModelStyleCommand',
|
|
71
|
+
MULTI: 'MultiCommand',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const DEFAULT_GEOMETRY_PARAMS = {
|
|
75
|
+
box: {
|
|
76
|
+
width: 1,
|
|
77
|
+
height: 1,
|
|
78
|
+
depth: 1,
|
|
79
|
+
},
|
|
80
|
+
tetrahedron: {
|
|
81
|
+
radius: 1,
|
|
82
|
+
},
|
|
83
|
+
octahedron: {
|
|
84
|
+
radius: 1,
|
|
85
|
+
},
|
|
86
|
+
cylinder: {
|
|
87
|
+
radiusTop: 1,
|
|
88
|
+
radiusBottom: 1,
|
|
89
|
+
height: 1,
|
|
90
|
+
},
|
|
91
|
+
ring: {
|
|
92
|
+
radius: 1,
|
|
93
|
+
tube: 0.4,
|
|
94
|
+
arc: 360,
|
|
95
|
+
},
|
|
96
|
+
sphere: {
|
|
97
|
+
radius: 1,
|
|
98
|
+
phiStart: 0,
|
|
99
|
+
phiLength: 360,
|
|
100
|
+
thetaStart: 0,
|
|
101
|
+
thetaLength: 180,
|
|
102
|
+
},
|
|
103
|
+
capsule: {
|
|
104
|
+
radius: 1,
|
|
105
|
+
length: 1,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { DEFAULT_ELEMENT_COLOR, DEFAULT_GEOMETRY_PARAMS } from './constants';
|
|
2
|
+
import {
|
|
3
|
+
applyTransform,
|
|
4
|
+
cloneUserData,
|
|
5
|
+
createDefaultElementName,
|
|
6
|
+
createDefaultGroupName,
|
|
7
|
+
createUniqueName,
|
|
8
|
+
getElementBaseLabel,
|
|
9
|
+
updateDisplayName,
|
|
10
|
+
} from './scene-helpers';
|
|
11
|
+
|
|
12
|
+
function normalizeNumber(value, fallback) {
|
|
13
|
+
const nextValue = Number(value);
|
|
14
|
+
return Number.isFinite(nextValue) ? nextValue : fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeCapsuleLengthValue(geometryParams = {}, fallback) {
|
|
18
|
+
if (geometryParams.length !== undefined) {
|
|
19
|
+
return normalizeNumber(geometryParams.length, fallback);
|
|
20
|
+
}
|
|
21
|
+
if (geometryParams.height !== undefined) {
|
|
22
|
+
return normalizeNumber(geometryParams.height, fallback);
|
|
23
|
+
}
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function normalizeGeometryParams(type, geometryParams = {}) {
|
|
28
|
+
const defaults = DEFAULT_GEOMETRY_PARAMS[type];
|
|
29
|
+
if (!defaults) {
|
|
30
|
+
throw new Error(`不支持的元素类型:${type}`);
|
|
31
|
+
}
|
|
32
|
+
const normalized = {};
|
|
33
|
+
Object.keys(defaults).forEach(key => {
|
|
34
|
+
if (type === 'capsule' && key === 'length') {
|
|
35
|
+
normalized[key] = normalizeCapsuleLengthValue(geometryParams, defaults[key]);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
normalized[key] = normalizeNumber(geometryParams[key], defaults[key]);
|
|
39
|
+
});
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createGeometry(THREE, type, geometryParams = {}) {
|
|
44
|
+
const params = normalizeGeometryParams(type, geometryParams);
|
|
45
|
+
switch (type) {
|
|
46
|
+
case 'box':
|
|
47
|
+
return new THREE.BoxGeometry(params.width, params.height, params.depth);
|
|
48
|
+
case 'tetrahedron':
|
|
49
|
+
return new THREE.TetrahedronGeometry(params.radius);
|
|
50
|
+
case 'octahedron':
|
|
51
|
+
return new THREE.OctahedronGeometry(params.radius);
|
|
52
|
+
case 'cylinder':
|
|
53
|
+
return new THREE.CylinderGeometry(params.radiusTop, params.radiusBottom, params.height, 32);
|
|
54
|
+
case 'ring':
|
|
55
|
+
return new THREE.TorusGeometry(
|
|
56
|
+
params.radius,
|
|
57
|
+
params.tube,
|
|
58
|
+
16,
|
|
59
|
+
64,
|
|
60
|
+
(params.arc * Math.PI) / 180
|
|
61
|
+
);
|
|
62
|
+
case 'sphere':
|
|
63
|
+
return new THREE.SphereGeometry(
|
|
64
|
+
params.radius,
|
|
65
|
+
32,
|
|
66
|
+
16,
|
|
67
|
+
(params.phiStart * Math.PI) / 180,
|
|
68
|
+
(params.phiLength * Math.PI) / 180,
|
|
69
|
+
(params.thetaStart * Math.PI) / 180,
|
|
70
|
+
(params.thetaLength * Math.PI) / 180
|
|
71
|
+
);
|
|
72
|
+
case 'capsule':
|
|
73
|
+
return new THREE.CapsuleGeometry(params.radius, params.length, 8, 16);
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`不支持的元素类型:${type}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createDefaultMaterial(THREE, materialOptions = {}) {
|
|
80
|
+
const color = materialOptions.color || DEFAULT_ELEMENT_COLOR;
|
|
81
|
+
const opacity =
|
|
82
|
+
materialOptions.opacity === undefined ? 1 : normalizeNumber(materialOptions.opacity, 1);
|
|
83
|
+
return new THREE.MeshStandardMaterial({
|
|
84
|
+
color,
|
|
85
|
+
transparent: opacity < 1,
|
|
86
|
+
opacity,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createPrimitive(THREE, type, options = {}) {
|
|
91
|
+
const geometryParams = normalizeGeometryParams(type, options.geometryParams);
|
|
92
|
+
const geometry = createGeometry(THREE, type, geometryParams);
|
|
93
|
+
const material = createDefaultMaterial(THREE, options.material);
|
|
94
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
95
|
+
mesh.name = options.name || getElementBaseLabel(type);
|
|
96
|
+
mesh.userData = {
|
|
97
|
+
nodeType: 'custom-element',
|
|
98
|
+
elementType: type,
|
|
99
|
+
// displayName: mesh.name,
|
|
100
|
+
editable: true,
|
|
101
|
+
deletable: true,
|
|
102
|
+
cloneable: true,
|
|
103
|
+
source: options.source || 'manual-create',
|
|
104
|
+
geometryParams,
|
|
105
|
+
};
|
|
106
|
+
applyTransform(mesh, options.transform);
|
|
107
|
+
updateDisplayName(mesh);
|
|
108
|
+
return mesh;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createGroup(THREE, options = {}) {
|
|
112
|
+
const group = new THREE.Group();
|
|
113
|
+
group.name = options.name || '组';
|
|
114
|
+
group.userData = {
|
|
115
|
+
nodeType: 'custom-group',
|
|
116
|
+
// displayName: group.name,
|
|
117
|
+
editable: true,
|
|
118
|
+
deletable: true,
|
|
119
|
+
cloneable: true,
|
|
120
|
+
source: options.source || 'manual-create',
|
|
121
|
+
};
|
|
122
|
+
applyTransform(group, options.transform);
|
|
123
|
+
updateDisplayName(group);
|
|
124
|
+
return group;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function createElementByType(THREE, type, parent, options = {}) {
|
|
128
|
+
const name = options.name || createDefaultElementName(parent, type);
|
|
129
|
+
return createPrimitive(THREE, type, {
|
|
130
|
+
...options,
|
|
131
|
+
name,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function createGroupElement(THREE, parent, options = {}) {
|
|
136
|
+
const name = options.name || createDefaultGroupName(parent);
|
|
137
|
+
return createGroup(THREE, {
|
|
138
|
+
...options,
|
|
139
|
+
name,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function cloneCustomObject(THREE, object, targetParent) {
|
|
144
|
+
const clonedObject = object.clone(true);
|
|
145
|
+
clonedObject.traverse(child => {
|
|
146
|
+
if (child.userData) {
|
|
147
|
+
child.userData = cloneUserData(child.userData);
|
|
148
|
+
}
|
|
149
|
+
if (child.isMesh) {
|
|
150
|
+
child.geometry = child.geometry.clone();
|
|
151
|
+
if (Array.isArray(child.material)) {
|
|
152
|
+
child.material = child.material.map(material => material.clone());
|
|
153
|
+
} else if (child.material) {
|
|
154
|
+
child.material = child.material.clone();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
clonedObject.userData.source = 'clone';
|
|
160
|
+
clonedObject.name = createUniqueName(targetParent, object.name);
|
|
161
|
+
updateDisplayName(clonedObject);
|
|
162
|
+
return clonedObject;
|
|
163
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default class SceneEventBus {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.listeners = new Map();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
on(eventName, handler) {
|
|
7
|
+
if (!this.listeners.has(eventName)) {
|
|
8
|
+
this.listeners.set(eventName, new Set());
|
|
9
|
+
}
|
|
10
|
+
this.listeners.get(eventName).add(handler);
|
|
11
|
+
return () => this.off(eventName, handler);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
off(eventName, handler) {
|
|
15
|
+
const handlers = this.listeners.get(eventName);
|
|
16
|
+
if (!handlers) return;
|
|
17
|
+
handlers.delete(handler);
|
|
18
|
+
if (handlers.size === 0) {
|
|
19
|
+
this.listeners.delete(eventName);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
emit(eventName, payload) {
|
|
24
|
+
const handlers = this.listeners.get(eventName);
|
|
25
|
+
if (!handlers || handlers.size === 0) return;
|
|
26
|
+
handlers.forEach(handler => {
|
|
27
|
+
handler(payload);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
clear() {
|
|
32
|
+
this.listeners.clear();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { HISTORY_MERGE_WINDOW } from './constants';
|
|
2
|
+
|
|
3
|
+
export default class History {
|
|
4
|
+
constructor(service, options = {}) {
|
|
5
|
+
this.service = service;
|
|
6
|
+
this.undos = [];
|
|
7
|
+
this.redos = [];
|
|
8
|
+
this.lastCommandTime = 0;
|
|
9
|
+
this.mergeWindow = options.mergeWindow || HISTORY_MERGE_WINDOW;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
execute(command) {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const lastCommand = this.undos[this.undos.length - 1];
|
|
15
|
+
if (
|
|
16
|
+
lastCommand &&
|
|
17
|
+
lastCommand.updatable &&
|
|
18
|
+
command.updatable &&
|
|
19
|
+
typeof lastCommand.canUpdate === 'function' &&
|
|
20
|
+
lastCommand.canUpdate(command, now - this.lastCommandTime, this.mergeWindow)
|
|
21
|
+
) {
|
|
22
|
+
lastCommand.update(command);
|
|
23
|
+
lastCommand.execute();
|
|
24
|
+
this.redos = [];
|
|
25
|
+
this.lastCommandTime = now;
|
|
26
|
+
return {
|
|
27
|
+
command: lastCommand,
|
|
28
|
+
merged: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
command.execute();
|
|
33
|
+
this.undos.push(command);
|
|
34
|
+
this.redos = [];
|
|
35
|
+
this.lastCommandTime = now;
|
|
36
|
+
return {
|
|
37
|
+
command,
|
|
38
|
+
merged: false,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
undo() {
|
|
43
|
+
const command = this.undos.pop();
|
|
44
|
+
if (!command) return null;
|
|
45
|
+
command.undo();
|
|
46
|
+
this.redos.push(command);
|
|
47
|
+
return command;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
redo() {
|
|
51
|
+
const command = this.redos.pop();
|
|
52
|
+
if (!command) return null;
|
|
53
|
+
command.execute();
|
|
54
|
+
this.undos.push(command);
|
|
55
|
+
return command;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clear() {
|
|
59
|
+
this.undos = [];
|
|
60
|
+
this.redos = [];
|
|
61
|
+
this.lastCommandTime = 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
canUndo() {
|
|
65
|
+
return this.undos.length > 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
canRedo() {
|
|
69
|
+
return this.redos.length > 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getState() {
|
|
73
|
+
return {
|
|
74
|
+
canUndo: this.canUndo(),
|
|
75
|
+
canRedo: this.canRedo(),
|
|
76
|
+
undoCount: this.undos.length,
|
|
77
|
+
redoCount: this.redos.length,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|