@archvisioninc/canvas 3.3.6 → 3.3.8
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/.claude/settings.local.json +8 -0
- package/README_DEV.md +4 -1
- package/package.json +1 -1
- package/src/package/helpers/canvasUpdateHelpers.js +145 -0
- package/src/package/helpers/initHelpers.js +10 -0
- package/src/package/helpers/utilityHelpers.js +17 -0
- package/dist/Canvas.js +0 -67
- package/dist/actions/index.js +0 -1
- package/dist/actions/shortcutActions.js +0 -313
- package/dist/constants/constants.js +0 -80
- package/dist/constants/index.js +0 -1
- package/dist/enums/aspectRatios.js +0 -17
- package/dist/enums/dimensions.js +0 -20
- package/dist/enums/downscaling.js +0 -16
- package/dist/enums/exclusions.js +0 -4
- package/dist/enums/formats.js +0 -1
- package/dist/enums/index.js +0 -8
- package/dist/enums/orthoOptions.js +0 -28
- package/dist/enums/scaleUnits.js +0 -25
- package/dist/enums/shortcuts.js +0 -89
- package/dist/helpers/cameraHelpers.js +0 -86
- package/dist/helpers/canvasAddHelpers.js +0 -161
- package/dist/helpers/canvasCommunicationHelpers.js +0 -52
- package/dist/helpers/canvasRemoveHelpers.js +0 -230
- package/dist/helpers/canvasUpdateHelpers.js +0 -1247
- package/dist/helpers/gizmoHelpers.js +0 -156
- package/dist/helpers/guiHelpers.js +0 -46
- package/dist/helpers/index.js +0 -16
- package/dist/helpers/initHelpers.js +0 -507
- package/dist/helpers/lightHelpers.js +0 -17
- package/dist/helpers/loadHelpers.js +0 -269
- package/dist/helpers/materialHelpers.js +0 -34
- package/dist/helpers/meshHelpers.js +0 -169
- package/dist/helpers/rayHelpers.js +0 -11
- package/dist/helpers/shortcutHelpers.js +0 -35
- package/dist/helpers/utilityHelpers.js +0 -697
- package/dist/helpers/viewportHelpers.js +0 -364
- package/dist/styles.js +0 -25
|
@@ -1,697 +0,0 @@
|
|
|
1
|
-
import { GLTF2 } from 'babylonjs-loaders';
|
|
2
|
-
import { GIZMOS, GUI, TRANSPARENCY_MODES } from '../constants';
|
|
3
|
-
import { orthoOptions } from '../enums';
|
|
4
|
-
import { downscaling } from '../enums/downscaling';
|
|
5
|
-
import { scene, utilLayer, gizmoManager, getUserMeshes, highlightManager, hoverHighlightManager, engine, guiTexture, ground, mirrorGround, getUserMaterials, getUserTextures, selectedMeshes, hiddenMeshes, getBoundingMeshData, singleMeshTNode, updateEnvironment, multiMeshTNode, resetSelectedMeshes, attachToSelectMeshesNode, getUserNodes, canvas } from '../helpers';
|
|
6
|
-
import { reactProps as props } from '../Canvas';
|
|
7
|
-
import * as BABYLON from 'babylonjs';
|
|
8
|
-
import * as SERIALIZERS from 'babylonjs-serializers';
|
|
9
|
-
import _ from 'lodash';
|
|
10
|
-
export let autoRotation;
|
|
11
|
-
export const newVector = (x, y, z) => new BABYLON.Vector3(x || 0, y || 0, z || 0);
|
|
12
|
-
export const toRadians = value => BABYLON.Tools.ToRadians(value || 0);
|
|
13
|
-
export const toDegrees = value => BABYLON.Tools.ToDegrees(value || 0);
|
|
14
|
-
export const addAutoRotation = () => {
|
|
15
|
-
autoRotation = new BABYLON.AutoRotationBehavior();
|
|
16
|
-
};
|
|
17
|
-
export const removeAutoRotation = () => {
|
|
18
|
-
autoRotation = null;
|
|
19
|
-
};
|
|
20
|
-
export const defaultCameraPosition = () => newVector(0, 3, 11);
|
|
21
|
-
export const serializeScene = () => {
|
|
22
|
-
const serializedData = {
|
|
23
|
-
metadata: scene?.metadata
|
|
24
|
-
};
|
|
25
|
-
return serializedData;
|
|
26
|
-
};
|
|
27
|
-
export const addHighlightExclusion = mesh => {
|
|
28
|
-
if (props.previewMode) return;
|
|
29
|
-
highlightManager.addExcludedMesh(mesh);
|
|
30
|
-
hoverHighlightManager.addExcludedMesh(mesh);
|
|
31
|
-
};
|
|
32
|
-
export const newMetaDataEntry = (key, value) => {
|
|
33
|
-
const {
|
|
34
|
-
metadata
|
|
35
|
-
} = scene;
|
|
36
|
-
const newMetadata = {
|
|
37
|
-
...metadata,
|
|
38
|
-
[key]: value
|
|
39
|
-
};
|
|
40
|
-
scene.metadata = newMetadata;
|
|
41
|
-
};
|
|
42
|
-
export const newTexture = (url, isEnvTexture, isSkyBox) => {
|
|
43
|
-
const onError = error => console.error(error);
|
|
44
|
-
const onLoad = () => {
|
|
45
|
-
if (isEnvTexture) {
|
|
46
|
-
const rotation = scene.metadata.environmentRotation || 0;
|
|
47
|
-
updateEnvironment({
|
|
48
|
-
payload: {
|
|
49
|
-
url,
|
|
50
|
-
transforms: {
|
|
51
|
-
rotation: {
|
|
52
|
-
ry: rotation
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
props.clearNotifications?.();
|
|
59
|
-
props.setSerializedData?.(serializeScene());
|
|
60
|
-
};
|
|
61
|
-
if (isEnvTexture) {
|
|
62
|
-
const isUserFile = _.startsWith(url, 'blob');
|
|
63
|
-
const extension = `.${url.split('.').pop()}`;
|
|
64
|
-
const type = BABYLON.Constants.TEXTUREFORMAT_RGBA;
|
|
65
|
-
const options = [url, scene, null, false, null, null, null, type, false, extension];
|
|
66
|
-
const hdrOptions = [url, scene, 256, false, true, false, true, onLoad, onError];
|
|
67
|
-
const texture = isUserFile ? new BABYLON.HDRCubeTexture(...hdrOptions) : new BABYLON.CubeTexture(...options);
|
|
68
|
-
isSkyBox ? texture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE : texture.coordinatesMode = BABYLON.Texture.CUBIC_MODE;
|
|
69
|
-
return texture;
|
|
70
|
-
}
|
|
71
|
-
const deleteBuffer = true;
|
|
72
|
-
const buffer = url;
|
|
73
|
-
const samplingMode = BABYLON.Texture.NEAREST_SAMPLINGMODE;
|
|
74
|
-
const texture = new BABYLON.Texture(url, scene, false, false, samplingMode, onLoad, onError, buffer, deleteBuffer);
|
|
75
|
-
return texture;
|
|
76
|
-
};
|
|
77
|
-
export const newColor = (r, g, b, a) => {
|
|
78
|
-
const isHex = _.startsWith(r, '#');
|
|
79
|
-
const hexHasAlpha = isHex && r.length === 9;
|
|
80
|
-
if (isHex) {
|
|
81
|
-
return hexHasAlpha ? new BABYLON.Color4.FromHexString(r) : new BABYLON.Color3.FromHexString(r);
|
|
82
|
-
}
|
|
83
|
-
return a ? new BABYLON.Color4(r, g, b, a) : new BABYLON.Color3(r, g, b);
|
|
84
|
-
};
|
|
85
|
-
export const newHighlightManager = name => {
|
|
86
|
-
const manager = new BABYLON.HighlightLayer(name, scene);
|
|
87
|
-
manager.blurHorizontalSize = 1.5;
|
|
88
|
-
manager.blurVerticalSize = 1.5;
|
|
89
|
-
manager.innerGlow = false;
|
|
90
|
-
return manager;
|
|
91
|
-
};
|
|
92
|
-
export const newFraming = () => new BABYLON.FramingBehavior();
|
|
93
|
-
export const newScreenshot = (isBillboard, callback) => {
|
|
94
|
-
let onSuccess = data => callback?.(data);
|
|
95
|
-
if (!props.previewMode) {
|
|
96
|
-
const outerFrame = guiTexture.getControlByName(GUI.outerSafeFrame);
|
|
97
|
-
const xAxisMesh = scene.getMeshByName('xAxisMesh');
|
|
98
|
-
const yAxisMesh = scene.getMeshByName('yAxisMesh');
|
|
99
|
-
const zAxisMesh = scene.getMeshByName('zAxisMesh');
|
|
100
|
-
const hdrSkyBox = scene.getMeshByName('hdrSkyBox');
|
|
101
|
-
const axesVisible = xAxisMesh.isVisible;
|
|
102
|
-
const skyBoxVisible = hdrSkyBox.isVisible;
|
|
103
|
-
const frameVisible = outerFrame.isVisible;
|
|
104
|
-
const groundVisible = ground.isVisible;
|
|
105
|
-
const mirrorGroundVisible = mirrorGround.isVisible;
|
|
106
|
-
const {
|
|
107
|
-
positionGizmoEnabled,
|
|
108
|
-
rotationGizmoEnabled,
|
|
109
|
-
boundingBoxGizmoEnabled,
|
|
110
|
-
scaleGizmoEnabled
|
|
111
|
-
} = gizmoManager;
|
|
112
|
-
const toggleUI = restore => {
|
|
113
|
-
if (isBillboard) {
|
|
114
|
-
mirrorGround.isVisible = restore ? mirrorGroundVisible : false;
|
|
115
|
-
}
|
|
116
|
-
ground.isVisible = restore ? groundVisible : false;
|
|
117
|
-
xAxisMesh.isVisible = restore ? axesVisible : false;
|
|
118
|
-
yAxisMesh.isVisible = restore ? axesVisible : false;
|
|
119
|
-
zAxisMesh.isVisible = restore ? axesVisible : false;
|
|
120
|
-
hdrSkyBox.isVisible = restore ? skyBoxVisible : false;
|
|
121
|
-
outerFrame.isVisible = restore ? frameVisible : false;
|
|
122
|
-
gizmoManager.positionGizmoEnabled = restore ? positionGizmoEnabled : false;
|
|
123
|
-
gizmoManager.rotationGizmoEnabled = restore ? rotationGizmoEnabled : false;
|
|
124
|
-
gizmoManager.boundingBoxGizmoEnabled = restore ? boundingBoxGizmoEnabled : false;
|
|
125
|
-
gizmoManager.scaleGizmoEnabled = restore ? scaleGizmoEnabled : false;
|
|
126
|
-
};
|
|
127
|
-
onSuccess = data => {
|
|
128
|
-
toggleUI(true);
|
|
129
|
-
callback?.(data);
|
|
130
|
-
};
|
|
131
|
-
toggleUI();
|
|
132
|
-
}
|
|
133
|
-
BABYLON.Tools.CreateScreenshot(engine, scene.activeCamera, {
|
|
134
|
-
height: canvas.height,
|
|
135
|
-
width: canvas.width
|
|
136
|
-
}, onSuccess);
|
|
137
|
-
};
|
|
138
|
-
export const newGizmo = (type, options = {}) => {
|
|
139
|
-
if (type === GIZMOS.PositionGizmo || type === GIZMOS.ScaleGizmo) {
|
|
140
|
-
const {
|
|
141
|
-
thickness
|
|
142
|
-
} = options;
|
|
143
|
-
return new BABYLON[type](utilLayer, thickness, gizmoManager);
|
|
144
|
-
}
|
|
145
|
-
if (type === GIZMOS.RotationGizmo) {
|
|
146
|
-
const {
|
|
147
|
-
tessellation,
|
|
148
|
-
useEulerRotation,
|
|
149
|
-
thickness,
|
|
150
|
-
rotationGizmoOptions
|
|
151
|
-
} = options;
|
|
152
|
-
return new BABYLON[type](utilLayer, tessellation, useEulerRotation, thickness, gizmoManager, rotationGizmoOptions);
|
|
153
|
-
}
|
|
154
|
-
if (type === GIZMOS.BoundingBoxGizmo) {
|
|
155
|
-
const {
|
|
156
|
-
color
|
|
157
|
-
} = options;
|
|
158
|
-
return new BABYLON[type](utilLayer, color, gizmoManager);
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
export const getColor = (props, key) => {
|
|
162
|
-
const hasProps = props.theme && props.$selectedTheme;
|
|
163
|
-
if (hasProps) {
|
|
164
|
-
const themeColors = props.theme.colors[props.$selectedTheme];
|
|
165
|
-
return themeColors[key];
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
export const blobToGLB = (blob, name) => {
|
|
169
|
-
const file = blob;
|
|
170
|
-
file.lastModifiedDate = new Date();
|
|
171
|
-
file.name = `${name}.glb`;
|
|
172
|
-
return file;
|
|
173
|
-
};
|
|
174
|
-
export const getGLTFData = async (type, download) => {
|
|
175
|
-
const shouldExportNode = node => {
|
|
176
|
-
const userNodes = getUserNodes();
|
|
177
|
-
const userMeshes = getUserMeshes();
|
|
178
|
-
const includesNode = userNodes.includes(node);
|
|
179
|
-
const includesMesh = userMeshes.includes(node);
|
|
180
|
-
return includesNode || includesMesh;
|
|
181
|
-
};
|
|
182
|
-
try {
|
|
183
|
-
const exporterType = type === 'glb' ? 'GLBAsync' : 'GLTFAsync';
|
|
184
|
-
resetSelectedMeshes();
|
|
185
|
-
attachToSelectMeshesNode();
|
|
186
|
-
const gltf = await SERIALIZERS.GLTF2Export[exporterType](scene, 'fileName', {
|
|
187
|
-
shouldExportNode
|
|
188
|
-
});
|
|
189
|
-
if (download) gltf.downloadFiles();
|
|
190
|
-
props.setDownloadedModel?.(gltf.glTFFiles);
|
|
191
|
-
} catch (e) {
|
|
192
|
-
console.error(e);
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
export const getMidpoint = (v1, v2) => {
|
|
196
|
-
const halfX = 0.5 * (v1._x + v2._x);
|
|
197
|
-
const halfY = 0.5 * (v1._y + v2._y);
|
|
198
|
-
const halfZ = 0.5 * (v1._z + v2._z);
|
|
199
|
-
const midpoint = newVector(halfX, halfY, halfZ);
|
|
200
|
-
return midpoint;
|
|
201
|
-
};
|
|
202
|
-
export const getRadius = (minimum, maximum) => {
|
|
203
|
-
const center = getMidpoint(minimum, maximum);
|
|
204
|
-
const delta = axis => Math.pow(Math.abs(axis - center[axis]), 2);
|
|
205
|
-
const radius = Math.sqrt(delta('_x') + delta('_y') + delta('_z'));
|
|
206
|
-
return radius;
|
|
207
|
-
};
|
|
208
|
-
export const restoreParents = parentList => {
|
|
209
|
-
parentList.forEach(item => {
|
|
210
|
-
const {
|
|
211
|
-
mesh,
|
|
212
|
-
parent
|
|
213
|
-
} = item;
|
|
214
|
-
mesh.setParent(parent);
|
|
215
|
-
});
|
|
216
|
-
};
|
|
217
|
-
export const getMeshesInCameraView = () => {
|
|
218
|
-
const camera = scene.activeCamera;
|
|
219
|
-
const userMeshes = getUserMeshes();
|
|
220
|
-
const possibleMeshes = userMeshes.map(mesh => {
|
|
221
|
-
if (mesh.isPickable && camera.isInFrustum(mesh)) {
|
|
222
|
-
return mesh;
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
return possibleMeshes.filter(mesh => mesh);
|
|
226
|
-
};
|
|
227
|
-
export const getStatisticsMetadata = props => {
|
|
228
|
-
const userMeshes = getUserMeshes();
|
|
229
|
-
const userMaterials = getUserMaterials();
|
|
230
|
-
const userTextures = getUserTextures();
|
|
231
|
-
const meshesVertices = userMeshes.map(mesh => mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind));
|
|
232
|
-
const meshesIndices = userMeshes.map(mesh => mesh.getIndices());
|
|
233
|
-
const vertices = meshesVertices.reduce((prev, current) => prev + current.length / 3, 0);
|
|
234
|
-
const triangles = meshesIndices.reduce((prev, current) => prev + current.length / 3, 0);
|
|
235
|
-
const getMeshStatisticsArray = () => {
|
|
236
|
-
const meshStats = userMeshes.map(mesh => {
|
|
237
|
-
const meshVertices = mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind).length / 3;
|
|
238
|
-
const meshIndices = mesh.getIndices().length / 3;
|
|
239
|
-
return {
|
|
240
|
-
id: mesh.id,
|
|
241
|
-
vertices: meshVertices,
|
|
242
|
-
triangles: meshIndices
|
|
243
|
-
};
|
|
244
|
-
});
|
|
245
|
-
return meshStats;
|
|
246
|
-
};
|
|
247
|
-
const value = {
|
|
248
|
-
materialCount: userMaterials.length?.toLocaleString(),
|
|
249
|
-
textures: userTextures.length?.toLocaleString(),
|
|
250
|
-
meshes: {
|
|
251
|
-
count: userMeshes.length?.toLocaleString(),
|
|
252
|
-
meshes: getMeshStatisticsArray()
|
|
253
|
-
},
|
|
254
|
-
vertices: vertices.toLocaleString(),
|
|
255
|
-
triangles: triangles.toLocaleString()
|
|
256
|
-
};
|
|
257
|
-
newMetaDataEntry('statistics', value);
|
|
258
|
-
props?.setSerializedData?.(serializeScene());
|
|
259
|
-
};
|
|
260
|
-
export const buildMeshIdArray = hidden => {
|
|
261
|
-
const arr = hidden ? hiddenMeshes.map(mesh => mesh.id) : selectedMeshes.map(mesh => ({
|
|
262
|
-
id: mesh.id,
|
|
263
|
-
materialId: mesh.material?.id || 'Default',
|
|
264
|
-
textures: mesh.material?.getActiveTextures().map(texture => texture.name)
|
|
265
|
-
}));
|
|
266
|
-
return arr;
|
|
267
|
-
};
|
|
268
|
-
export const buildMaterialVariantsArray = () => {
|
|
269
|
-
const rootMesh = scene.getMeshByID('__root__');
|
|
270
|
-
const khrExtension = GLTF2.KHR_materials_variants;
|
|
271
|
-
const variants = khrExtension.GetAvailableVariants(rootMesh);
|
|
272
|
-
return variants;
|
|
273
|
-
};
|
|
274
|
-
export const buildSelectedMaterialArray = () => {
|
|
275
|
-
if (props.materialMode) {
|
|
276
|
-
const mainMaterial = scene.metadata?.materials?.find?.(item => item.materialId === 'material');
|
|
277
|
-
return [mainMaterial];
|
|
278
|
-
}
|
|
279
|
-
const arr = selectedMeshes?.map?.(mesh => {
|
|
280
|
-
const material = mesh.material;
|
|
281
|
-
if (material) {
|
|
282
|
-
const existingMaterial = scene.metadata.materials?.find(mat => mat.materialId === material.id);
|
|
283
|
-
return {
|
|
284
|
-
...(existingMaterial || {}),
|
|
285
|
-
...materialData(material)
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
}).filter(val => val);
|
|
289
|
-
if (_.isEmpty(arr)) {
|
|
290
|
-
if (!_.isEmpty(scene.metadata.selectedMaterials)) {
|
|
291
|
-
const currentSelectedMaterial = scene.metadata.selectedMaterials[0];
|
|
292
|
-
const materialMatch = scene.metadata.materials?.find(material => currentSelectedMaterial.materialId === material.materialId) || {};
|
|
293
|
-
return [materialMatch];
|
|
294
|
-
}
|
|
295
|
-
return [scene.metadata.materials?.[0] || {}];
|
|
296
|
-
}
|
|
297
|
-
return arr;
|
|
298
|
-
};
|
|
299
|
-
const updatedMeshTrackingRotation = id => {
|
|
300
|
-
const zAxisUp = scene.metadata.selectedAxisCompensation === 'Z-Axis up';
|
|
301
|
-
const xRotation = zAxisUp ? 270 : 0;
|
|
302
|
-
const meshSelected = selectedMeshes.length > 0;
|
|
303
|
-
const workingMesh = scene.metadata.meshChangeTracking?.find(mesh => mesh.meshId === id) || {};
|
|
304
|
-
const workingValues = {
|
|
305
|
-
rx: xRotation,
|
|
306
|
-
ry: 0,
|
|
307
|
-
rz: 0
|
|
308
|
-
};
|
|
309
|
-
if (meshSelected) {
|
|
310
|
-
const tNode = selectedMeshes.length > 1 ? multiMeshTNode : singleMeshTNode;
|
|
311
|
-
const {
|
|
312
|
-
rotation
|
|
313
|
-
} = tNode;
|
|
314
|
-
const newRotation = Object.values(rotation).map(rad => toDegrees(rad))?.slice(1);
|
|
315
|
-
const childMeshes = tNode.getChildMeshes();
|
|
316
|
-
const includesMesh = childMeshes.some(mesh => mesh.id === id);
|
|
317
|
-
if (includesMesh && newRotation) {
|
|
318
|
-
const [rx, ry, rz] = newRotation;
|
|
319
|
-
const rotateX = rx <= 0 ? (360 - rx * -1) % 360 : Math.abs(rx) % 360;
|
|
320
|
-
const rotateY = ry <= 0 ? (360 - ry * -1) % 360 : Math.abs(ry) % 360;
|
|
321
|
-
const rotateZ = rz <= 0 ? (360 - rz * -1) % 360 : Math.abs(rz) % 360;
|
|
322
|
-
return {
|
|
323
|
-
rx: rotateX,
|
|
324
|
-
ry: rotateY,
|
|
325
|
-
rz: rotateZ
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
if (!_.isEmpty(workingMesh)) {
|
|
329
|
-
const {
|
|
330
|
-
rx,
|
|
331
|
-
ry,
|
|
332
|
-
rz
|
|
333
|
-
} = workingMesh;
|
|
334
|
-
return {
|
|
335
|
-
rx,
|
|
336
|
-
ry,
|
|
337
|
-
rz
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return workingValues;
|
|
342
|
-
};
|
|
343
|
-
const baseMeshScales = [];
|
|
344
|
-
const updateMeshTrackingScale = mesh => {
|
|
345
|
-
const baseMesh = baseMeshScales.find(bMesh => bMesh.id === mesh.id);
|
|
346
|
-
if (_.isEmpty(baseMesh)) {
|
|
347
|
-
const parent = mesh.parent;
|
|
348
|
-
mesh.setParent(singleMeshTNode);
|
|
349
|
-
const meshScaleObj = {
|
|
350
|
-
id: mesh.id,
|
|
351
|
-
sx: mesh.scaling.x,
|
|
352
|
-
sy: mesh.scaling.y,
|
|
353
|
-
sz: mesh.scaling.z
|
|
354
|
-
};
|
|
355
|
-
baseMeshScales.push(meshScaleObj);
|
|
356
|
-
mesh.setParent(parent);
|
|
357
|
-
return meshScaleObj;
|
|
358
|
-
}
|
|
359
|
-
return baseMesh;
|
|
360
|
-
};
|
|
361
|
-
export const buildMeshPositionsArray = args => {
|
|
362
|
-
return getUserMeshes().map(mesh => {
|
|
363
|
-
const {
|
|
364
|
-
rx,
|
|
365
|
-
ry,
|
|
366
|
-
rz
|
|
367
|
-
} = updatedMeshTrackingRotation(mesh.id);
|
|
368
|
-
const {
|
|
369
|
-
sx,
|
|
370
|
-
sy,
|
|
371
|
-
sz
|
|
372
|
-
} = updateMeshTrackingScale(mesh);
|
|
373
|
-
const boundingInfo = getBoundingMeshData([mesh]);
|
|
374
|
-
const {
|
|
375
|
-
center,
|
|
376
|
-
centerWorld
|
|
377
|
-
} = boundingInfo;
|
|
378
|
-
const pos = center || centerWorld;
|
|
379
|
-
const scaleX = mesh?.scaling.x || 1;
|
|
380
|
-
const scaleY = mesh?.scaling.y || 1;
|
|
381
|
-
const scaleZ = mesh?.scaling.z || 1;
|
|
382
|
-
const meshChangeTracking = scene.metadata?.meshChangeTracking;
|
|
383
|
-
const prevMeshTransforms = meshChangeTracking?.find(trackedMesh => trackedMesh.meshId === mesh.id) || {};
|
|
384
|
-
let defaultMeshPosition = {
|
|
385
|
-
...prevMeshTransforms,
|
|
386
|
-
meshId: mesh?.id || '',
|
|
387
|
-
tx: pos.x,
|
|
388
|
-
ty: pos.y,
|
|
389
|
-
tz: pos.z
|
|
390
|
-
};
|
|
391
|
-
if (!args?.preserveScale) {
|
|
392
|
-
defaultMeshPosition = {
|
|
393
|
-
...defaultMeshPosition,
|
|
394
|
-
sx: scaleX === 1 ? 1 : scaleX / sx,
|
|
395
|
-
sy: scaleY === 1 ? 1 : scaleY / sy,
|
|
396
|
-
sz: scaleZ === 1 ? 1 : scaleZ / sz
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
if (!args?.preserveRotation) {
|
|
400
|
-
defaultMeshPosition = {
|
|
401
|
-
...defaultMeshPosition,
|
|
402
|
-
rx,
|
|
403
|
-
ry,
|
|
404
|
-
rz
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
return defaultMeshPosition;
|
|
408
|
-
});
|
|
409
|
-
};
|
|
410
|
-
export const getImageFileFromBuffer = args => {
|
|
411
|
-
const {
|
|
412
|
-
buffer,
|
|
413
|
-
asDataUrl
|
|
414
|
-
} = args || {};
|
|
415
|
-
if (_.isEmpty(buffer)) return '';
|
|
416
|
-
if (!_.isObject(buffer) && !_.isArray(buffer)) return buffer;
|
|
417
|
-
if (asDataUrl) {
|
|
418
|
-
// Convert buffer to data:image/png;base64 string
|
|
419
|
-
const Uint8ToString = u8a => {
|
|
420
|
-
if (_.isEmpty(u8a)) return '';
|
|
421
|
-
const maxChunkSize = 8192;
|
|
422
|
-
const chunks = [];
|
|
423
|
-
for (let i = 0; i < u8a.length; i += maxChunkSize) {
|
|
424
|
-
const newChunk = String.fromCharCode.apply(null, u8a.subarray(i, i + maxChunkSize));
|
|
425
|
-
chunks.push(newChunk);
|
|
426
|
-
}
|
|
427
|
-
return chunks.join('');
|
|
428
|
-
};
|
|
429
|
-
const ascii = new Uint8Array(buffer);
|
|
430
|
-
const dataString = btoa(Uint8ToString(ascii));
|
|
431
|
-
const dataUrl = `data:image/png;base64,${dataString}`;
|
|
432
|
-
return dataUrl;
|
|
433
|
-
}
|
|
434
|
-
const blob = new Blob([buffer], {
|
|
435
|
-
type: 'image/png'
|
|
436
|
-
});
|
|
437
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
438
|
-
return blobUrl;
|
|
439
|
-
};
|
|
440
|
-
export const getTextureUrl = args => {
|
|
441
|
-
const {
|
|
442
|
-
texture,
|
|
443
|
-
asDataUrl
|
|
444
|
-
} = args || {};
|
|
445
|
-
const buffer = texture?._buffer;
|
|
446
|
-
if (_.isEmpty(buffer)) return texture?.url || '';
|
|
447
|
-
return getImageFileFromBuffer({
|
|
448
|
-
buffer,
|
|
449
|
-
asDataUrl
|
|
450
|
-
});
|
|
451
|
-
};
|
|
452
|
-
export const getExistingUVSettings = args => {
|
|
453
|
-
const {
|
|
454
|
-
materialId
|
|
455
|
-
} = args || {};
|
|
456
|
-
let settingsFound = false;
|
|
457
|
-
let uvSettings = {
|
|
458
|
-
uvXScale: 1,
|
|
459
|
-
uvYScale: 1,
|
|
460
|
-
uvXRotation: 0,
|
|
461
|
-
uvYRotation: 0,
|
|
462
|
-
uvZRotation: 0,
|
|
463
|
-
uvXOffset: 0,
|
|
464
|
-
uvYOffset: 0
|
|
465
|
-
};
|
|
466
|
-
const userMaterials = getUserMaterials();
|
|
467
|
-
const material = userMaterials.find(mat => mat.id === materialId);
|
|
468
|
-
if (material) {
|
|
469
|
-
Object.keys(material)?.find?.(key => {
|
|
470
|
-
const validMaterialWithTexture = _.isObject(material[key]) && !_.isArray(material[key]) && key.endsWith('Texture');
|
|
471
|
-
const texture = material[key];
|
|
472
|
-
const isEnvironment = key.toLowerCase().includes('environment');
|
|
473
|
-
const hasAllKeys = texture && ['uAng', 'wAng', 'vAng', 'uOffset', 'vOffset', 'uScale', 'vScale'].every(key => texture[key] !== undefined);
|
|
474
|
-
|
|
475
|
-
// NOTE: All UV's are adjusted globally, at the same time in canvasUpdateHelpers.js
|
|
476
|
-
// when **any** UV setting is changed. So we only need to find the first one to
|
|
477
|
-
// restore the UV settings.
|
|
478
|
-
if (!isEnvironment && validMaterialWithTexture && hasAllKeys && !settingsFound) {
|
|
479
|
-
settingsFound = true;
|
|
480
|
-
uvSettings = {
|
|
481
|
-
uvXScale: texture.uScale,
|
|
482
|
-
uvYScale: texture.vScale,
|
|
483
|
-
uvXRotation: texture.uAng,
|
|
484
|
-
uvYRotation: texture.wAng,
|
|
485
|
-
uvZRotation: texture.vAng,
|
|
486
|
-
uvXOffset: texture.uOffset,
|
|
487
|
-
uvYOffset: texture.vOffset
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
return uvSettings;
|
|
493
|
-
};
|
|
494
|
-
export const materialData = material => {
|
|
495
|
-
const meshesWithMaterial = getUserMeshes().map(mesh => {
|
|
496
|
-
if (mesh.material.name === material.id) return mesh;
|
|
497
|
-
}).filter(mesh => mesh);
|
|
498
|
-
const allMeshes = scene.metadata?.statistics.meshes;
|
|
499
|
-
const totalTriangles = allMeshes.meshes.reduce((prev, current) => {
|
|
500
|
-
const includedMesh = meshesWithMaterial.some(mesh => mesh.id === current.id);
|
|
501
|
-
if (includedMesh) return prev + current.triangles;
|
|
502
|
-
return prev;
|
|
503
|
-
}, 0);
|
|
504
|
-
return {
|
|
505
|
-
materialId: material.id,
|
|
506
|
-
name: material.name,
|
|
507
|
-
// General
|
|
508
|
-
backfaceCullingEnabled: material.backFaceCulling || false,
|
|
509
|
-
wireFrameEnabled: material.wireframe || false,
|
|
510
|
-
pointsCloudEnabled: material.pointsCloud || false,
|
|
511
|
-
// Basic
|
|
512
|
-
albedoColor: material.albedoColor?.toHexString() || props?.theme?.colors?.[props.$selectedTheme]?.black,
|
|
513
|
-
albedoTexture: {
|
|
514
|
-
name: material.albedoTexture?.name,
|
|
515
|
-
url: getTextureUrl({
|
|
516
|
-
texture: material.albedoTexture,
|
|
517
|
-
asDataUrl: true
|
|
518
|
-
})
|
|
519
|
-
},
|
|
520
|
-
albedoHasAlpha: material.albedoTexture?.hasAlpha || false,
|
|
521
|
-
environmentIntensity: material.environmentIntensity,
|
|
522
|
-
metallic: material.metallic,
|
|
523
|
-
metallicTexture: {
|
|
524
|
-
name: material.metallicTexture?.name,
|
|
525
|
-
url: getTextureUrl({
|
|
526
|
-
texture: material.metallicTexture,
|
|
527
|
-
asDataUrl: true
|
|
528
|
-
})
|
|
529
|
-
},
|
|
530
|
-
roughness: material.roughness,
|
|
531
|
-
microSurfaceTexture: {
|
|
532
|
-
name: material.microSurfaceTexture?.name,
|
|
533
|
-
url: getTextureUrl({
|
|
534
|
-
texture: material.microSurfaceTexture,
|
|
535
|
-
asDataUrl: true
|
|
536
|
-
})
|
|
537
|
-
},
|
|
538
|
-
emissiveColor: material.emissiveColor?.toHexString() || props?.theme?.colors?.[props.$selectedTheme]?.black,
|
|
539
|
-
emissiveIntensity: material.emissiveIntensity,
|
|
540
|
-
emissiveTexture: {
|
|
541
|
-
name: material.emissiveTexture?.name,
|
|
542
|
-
url: getTextureUrl({
|
|
543
|
-
texture: material.emissiveTexture,
|
|
544
|
-
asDataUrl: true
|
|
545
|
-
})
|
|
546
|
-
},
|
|
547
|
-
ambientTextureStength: material.ambientTextureStrength,
|
|
548
|
-
ambientTexture: {
|
|
549
|
-
name: material.ambientTexture?.name,
|
|
550
|
-
url: getTextureUrl({
|
|
551
|
-
texture: material.ambientTexture,
|
|
552
|
-
asDataUrl: true
|
|
553
|
-
})
|
|
554
|
-
},
|
|
555
|
-
ambientColor: material.ambientColor?.toHexString() || props?.theme?.colors?.[props.$selectedTheme]?.black,
|
|
556
|
-
bumpIntensity: material.bumpTexture?.level ?? 1,
|
|
557
|
-
bumpTexture: {
|
|
558
|
-
name: material.bumpTexture?.name,
|
|
559
|
-
url: getTextureUrl({
|
|
560
|
-
texture: material.bumpTexture,
|
|
561
|
-
asDataUrl: true
|
|
562
|
-
})
|
|
563
|
-
},
|
|
564
|
-
// UV
|
|
565
|
-
uvSettings: {
|
|
566
|
-
...getExistingUVSettings({
|
|
567
|
-
materialId: material.id
|
|
568
|
-
})
|
|
569
|
-
},
|
|
570
|
-
// Advanced
|
|
571
|
-
clearCoatEnabled: material.clearCoat?.isEnabled || false,
|
|
572
|
-
clearCoatIntensity: material.clearCoat?.intensity,
|
|
573
|
-
clearCoatRoughness: material.clearCoat?.roughness,
|
|
574
|
-
clearCoatIOR: material.clearCoat?.indexOfRefraction,
|
|
575
|
-
alpha: material.alpha,
|
|
576
|
-
opacityTexture: {
|
|
577
|
-
name: material.opacityTexture?.name,
|
|
578
|
-
url: getTextureUrl({
|
|
579
|
-
texture: material.opacityTexture,
|
|
580
|
-
asDataUrl: true
|
|
581
|
-
})
|
|
582
|
-
},
|
|
583
|
-
transparencyMode: material.transparencyMode,
|
|
584
|
-
sssRefractionEnabled: material.subSurface?.isRefractionEnabled || false,
|
|
585
|
-
sssRefractionIntensity: material.subSurface?.refractionIntensity,
|
|
586
|
-
sssIOR: material.subSurface?.indexOfRefraction,
|
|
587
|
-
transparencyEnabled: material?.transparencyEnabled || false,
|
|
588
|
-
transparencyType: material?.albedoTexture?.hasAlpha ? TRANSPARENCY_MODES.stencil : material?.transparencyType || TRANSPARENCY_MODES.simple,
|
|
589
|
-
meshSimplification: {
|
|
590
|
-
enabled: material.meshSimplification?.enabled || false,
|
|
591
|
-
type: material.meshSimplification?.type || 'custom',
|
|
592
|
-
// [ 'low, 'medium', 'high', 'custom' ]
|
|
593
|
-
optimizationType: material.meshSimplification?.optimizationType || 'estimated triangles',
|
|
594
|
-
totalTriangles,
|
|
595
|
-
estimatedTriangles: material.meshSimplification?.estimatedTriangles || 99,
|
|
596
|
-
// Percent
|
|
597
|
-
userTargetTriangles: material.meshSimplification?.userTargetTriangles || totalTriangles || 0,
|
|
598
|
-
targetTriangles: Math.round((totalTriangles || +scene.metadata.statistics?.triangles?.replace?.(/,/g, '')) * ((material.meshSimplification?.estimatedTriangles || 99) / 100)) || 0
|
|
599
|
-
},
|
|
600
|
-
textureDownscaling: {
|
|
601
|
-
enabled: material.textureDownscaling?.enabled || false,
|
|
602
|
-
size: material.textureDownscaling?.size || downscaling.find(size => size.name === '4K').value
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
};
|
|
606
|
-
export const buildMaterialsArray = () => {
|
|
607
|
-
const materials = getUserMaterials();
|
|
608
|
-
const materialsArray = materials.map(material => materialData(material));
|
|
609
|
-
return materialsArray;
|
|
610
|
-
};
|
|
611
|
-
export const buildLightsArray = () => {
|
|
612
|
-
const lights = scene.lights;
|
|
613
|
-
return lights.map(light => {
|
|
614
|
-
const {
|
|
615
|
-
position
|
|
616
|
-
} = light;
|
|
617
|
-
const {
|
|
618
|
-
x,
|
|
619
|
-
y,
|
|
620
|
-
z
|
|
621
|
-
} = position;
|
|
622
|
-
return {
|
|
623
|
-
...light.serialize(),
|
|
624
|
-
rotation: light?.rotation ?? 0,
|
|
625
|
-
distance: light?.distance ?? Math.sqrt(Math.pow(Math.abs(x), 2) + Math.pow(Math.abs(y), 2) + Math.pow(Math.abs(z), 2))
|
|
626
|
-
};
|
|
627
|
-
});
|
|
628
|
-
};
|
|
629
|
-
const billboardScreenshot = view => {
|
|
630
|
-
const camera = scene.activeCamera.clone(view);
|
|
631
|
-
if (view !== 'perspective') {
|
|
632
|
-
const alpha = orthoOptions[view].alpha;
|
|
633
|
-
const beta = orthoOptions[view].beta;
|
|
634
|
-
camera.alpha = alpha;
|
|
635
|
-
camera.beta = beta;
|
|
636
|
-
camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA;
|
|
637
|
-
}
|
|
638
|
-
return BABYLON.Tools.CreateScreenshotUsingRenderTargetAsync(engine, camera, 2048);
|
|
639
|
-
};
|
|
640
|
-
export const takePreviewScreenshots = () => {
|
|
641
|
-
const baseView = ['perspective'];
|
|
642
|
-
const outerFrame = guiTexture.getControlByName(GUI.outerSafeFrame);
|
|
643
|
-
const xAxisMesh = scene.getMeshByName('xAxisMesh');
|
|
644
|
-
const yAxisMesh = scene.getMeshByName('yAxisMesh');
|
|
645
|
-
const zAxisMesh = scene.getMeshByName('zAxisMesh');
|
|
646
|
-
const hdrSkyBox = scene.getMeshByName('hdrSkyBox');
|
|
647
|
-
const axesVisible = xAxisMesh?.isVisible;
|
|
648
|
-
const skyBoxVisible = hdrSkyBox?.isVisible;
|
|
649
|
-
const frameVisible = outerFrame?.isVisible;
|
|
650
|
-
const groundVisible = ground?.isVisible;
|
|
651
|
-
const mirrorGroundVisible = mirrorGround?.isVisible;
|
|
652
|
-
const {
|
|
653
|
-
positionGizmoEnabled,
|
|
654
|
-
rotationGizmoEnabled,
|
|
655
|
-
boundingBoxGizmoEnabled,
|
|
656
|
-
scaleGizmoEnabled
|
|
657
|
-
} = gizmoManager;
|
|
658
|
-
const toggleUI = restore => {
|
|
659
|
-
const setVisibility = (object, isVisible) => {
|
|
660
|
-
if (object !== null && object !== undefined) {
|
|
661
|
-
object.isVisible = isVisible;
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
setVisibility(mirrorGround, restore ? mirrorGroundVisible : false);
|
|
665
|
-
setVisibility(ground, restore ? groundVisible : false);
|
|
666
|
-
setVisibility(xAxisMesh, restore ? axesVisible : false);
|
|
667
|
-
setVisibility(yAxisMesh, restore ? axesVisible : false);
|
|
668
|
-
setVisibility(zAxisMesh, restore ? axesVisible : false);
|
|
669
|
-
setVisibility(hdrSkyBox, restore ? skyBoxVisible : false);
|
|
670
|
-
setVisibility(outerFrame, restore ? frameVisible : false);
|
|
671
|
-
gizmoManager.positionGizmoEnabled = restore ? positionGizmoEnabled : false;
|
|
672
|
-
gizmoManager.rotationGizmoEnabled = restore ? rotationGizmoEnabled : false;
|
|
673
|
-
gizmoManager.boundingBoxGizmoEnabled = restore ? boundingBoxGizmoEnabled : false;
|
|
674
|
-
gizmoManager.scaleGizmoEnabled = restore ? scaleGizmoEnabled : false;
|
|
675
|
-
};
|
|
676
|
-
toggleUI();
|
|
677
|
-
try {
|
|
678
|
-
const screenshots = baseView.map(async view => {
|
|
679
|
-
const imageData = await billboardScreenshot(view);
|
|
680
|
-
return {
|
|
681
|
-
name: view,
|
|
682
|
-
imageData
|
|
683
|
-
};
|
|
684
|
-
});
|
|
685
|
-
Promise.all(screenshots).then(screenshots => {
|
|
686
|
-
const hasAllScreenshots = screenshots.length === baseView.length;
|
|
687
|
-
if (hasAllScreenshots) props.setBillboardImages?.(screenshots);
|
|
688
|
-
screenshots.forEach(screenshot => {
|
|
689
|
-
scene.getCameraByName(screenshot.name)?.dispose();
|
|
690
|
-
});
|
|
691
|
-
});
|
|
692
|
-
} catch (e) {
|
|
693
|
-
console.error('Unable to take screenshots.', e);
|
|
694
|
-
} finally {
|
|
695
|
-
toggleUI(true);
|
|
696
|
-
}
|
|
697
|
-
};
|