@2112-lab/central-plant 0.1.76 → 0.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle/index.js +656 -75
- package/dist/cjs/src/core/centralPlant.js +1 -1
- package/dist/cjs/src/core/centralPlantInternals.js +73 -0
- package/dist/cjs/src/managers/controls/componentDragManager.js +145 -32
- package/dist/cjs/src/managers/controls/transformControlsManager.js +22 -30
- package/dist/cjs/src/managers/pathfinding/pathfindingManager.js +52 -8
- package/dist/cjs/src/managers/scene/sceneOperationsManager.js +41 -4
- package/dist/cjs/src/utils/boundingBoxUtils.js +356 -0
- package/dist/esm/src/core/centralPlant.js +1 -1
- package/dist/esm/src/core/centralPlantInternals.js +74 -1
- package/dist/esm/src/managers/controls/componentDragManager.js +146 -33
- package/dist/esm/src/managers/controls/transformControlsManager.js +22 -30
- package/dist/esm/src/managers/pathfinding/pathfindingManager.js +52 -8
- package/dist/esm/src/managers/scene/sceneOperationsManager.js +41 -4
- package/dist/esm/src/utils/boundingBoxUtils.js +329 -0
- package/package.json +1 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
|
|
6
|
+
var THREE = require('three');
|
|
7
|
+
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n["default"] = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a wireframe box helper (LineSegments) from a Box3, visually identical
|
|
30
|
+
* to THREE.BoxHelper but driven by an explicit Box3 instead of setFromObject().
|
|
31
|
+
*
|
|
32
|
+
* @param {THREE.Box3} box3 - The bounding box to visualize
|
|
33
|
+
* @param {number} color - Line color (hex)
|
|
34
|
+
* @returns {THREE.LineSegments} A wireframe box matching BoxHelper's visual style
|
|
35
|
+
* @private
|
|
36
|
+
*/
|
|
37
|
+
function _createBoxHelperFromBox3(box3, color) {
|
|
38
|
+
var indices = new Uint16Array([0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7]);
|
|
39
|
+
var positions = new Float32Array(8 * 3);
|
|
40
|
+
var geometry = new THREE__namespace.BufferGeometry();
|
|
41
|
+
geometry.setIndex(new THREE__namespace.BufferAttribute(indices, 1));
|
|
42
|
+
geometry.setAttribute('position', new THREE__namespace.BufferAttribute(positions, 3));
|
|
43
|
+
var helper = new THREE__namespace.LineSegments(geometry, new THREE__namespace.LineBasicMaterial({
|
|
44
|
+
color: color,
|
|
45
|
+
toneMapped: false
|
|
46
|
+
}));
|
|
47
|
+
helper.matrixAutoUpdate = false;
|
|
48
|
+
|
|
49
|
+
// Populate positions from box3
|
|
50
|
+
_updateBoxHelperPositions(helper, box3);
|
|
51
|
+
return helper;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Updates a box helper's geometry positions from a Box3.
|
|
56
|
+
* Matches the vertex layout used by THREE.BoxHelper.
|
|
57
|
+
*
|
|
58
|
+
* @param {THREE.LineSegments} helper - The wireframe helper to update
|
|
59
|
+
* @param {THREE.Box3} box3 - The bounding box
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
function _updateBoxHelperPositions(helper, box3) {
|
|
63
|
+
if (box3.isEmpty()) return;
|
|
64
|
+
var min = box3.min;
|
|
65
|
+
var max = box3.max;
|
|
66
|
+
var position = helper.geometry.attributes.position;
|
|
67
|
+
var array = position.array;
|
|
68
|
+
|
|
69
|
+
// Same vertex layout as THREE.BoxHelper
|
|
70
|
+
array[0] = max.x;
|
|
71
|
+
array[1] = max.y;
|
|
72
|
+
array[2] = max.z;
|
|
73
|
+
array[3] = min.x;
|
|
74
|
+
array[4] = max.y;
|
|
75
|
+
array[5] = max.z;
|
|
76
|
+
array[6] = min.x;
|
|
77
|
+
array[7] = min.y;
|
|
78
|
+
array[8] = max.z;
|
|
79
|
+
array[9] = max.x;
|
|
80
|
+
array[10] = min.y;
|
|
81
|
+
array[11] = max.z;
|
|
82
|
+
array[12] = max.x;
|
|
83
|
+
array[13] = max.y;
|
|
84
|
+
array[14] = min.z;
|
|
85
|
+
array[15] = min.x;
|
|
86
|
+
array[16] = max.y;
|
|
87
|
+
array[17] = min.z;
|
|
88
|
+
array[18] = min.x;
|
|
89
|
+
array[19] = min.y;
|
|
90
|
+
array[20] = min.z;
|
|
91
|
+
array[21] = max.x;
|
|
92
|
+
array[22] = min.y;
|
|
93
|
+
array[23] = min.z;
|
|
94
|
+
position.needsUpdate = true;
|
|
95
|
+
helper.geometry.computeBoundingSphere();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Computes a bounding box for a Three.js object, excluding descendant meshes
|
|
100
|
+
* that belong to subtrees rooted at objects with excluded objectTypes.
|
|
101
|
+
*
|
|
102
|
+
* This mirrors what THREE.Box3.expandByObject() does internally, but with a
|
|
103
|
+
* filter predicate that skips meshes whose ancestry (up to the root object)
|
|
104
|
+
* includes any excluded objectType.
|
|
105
|
+
*
|
|
106
|
+
* @param {THREE.Object3D} object - The root object to compute bbox for
|
|
107
|
+
* @param {string[]} excludeTypes - userData.objectType values to exclude (e.g., ['io-device', 'connector'])
|
|
108
|
+
* @returns {THREE.Box3} The filtered bounding box in world space
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* // Compute bbox for pump body only, excluding io-devices and connectors
|
|
112
|
+
* const pumpBodyBBox = computeFilteredBoundingBox(pumpModel, ['io-device', 'connector'])
|
|
113
|
+
*/
|
|
114
|
+
function computeFilteredBoundingBox(object) {
|
|
115
|
+
var excludeTypes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
|
116
|
+
var box = new THREE__namespace.Box3();
|
|
117
|
+
var tempBox = new THREE__namespace.Box3();
|
|
118
|
+
var hasGeometry = false;
|
|
119
|
+
|
|
120
|
+
// Build a Set for O(1) lookups
|
|
121
|
+
var excludeSet = new Set(excludeTypes);
|
|
122
|
+
object.updateWorldMatrix(false, true);
|
|
123
|
+
object.traverse(function (child) {
|
|
124
|
+
// Only process nodes with geometry (Mesh, SkinnedMesh, etc.)
|
|
125
|
+
if (!child.geometry) return;
|
|
126
|
+
|
|
127
|
+
// Walk up the ancestry from child to root object, checking for excluded types.
|
|
128
|
+
// If any ancestor (excluding the root object itself) has an excluded objectType,
|
|
129
|
+
// this mesh belongs to an excluded subtree — skip it.
|
|
130
|
+
var ancestor = child;
|
|
131
|
+
while (ancestor && ancestor !== object) {
|
|
132
|
+
var _ancestor$userData;
|
|
133
|
+
if ((_ancestor$userData = ancestor.userData) !== null && _ancestor$userData !== void 0 && _ancestor$userData.objectType && excludeSet.has(ancestor.userData.objectType)) {
|
|
134
|
+
return; // Skip — this mesh belongs to an excluded subtree
|
|
135
|
+
}
|
|
136
|
+
ancestor = ancestor.parent;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Include this mesh's geometry in the bounding box
|
|
140
|
+
child.geometry.computeBoundingBox();
|
|
141
|
+
if (child.geometry.boundingBox) {
|
|
142
|
+
tempBox.copy(child.geometry.boundingBox);
|
|
143
|
+
tempBox.applyMatrix4(child.matrixWorld);
|
|
144
|
+
if (!hasGeometry) {
|
|
145
|
+
box.copy(tempBox);
|
|
146
|
+
hasGeometry = true;
|
|
147
|
+
} else {
|
|
148
|
+
box.union(tempBox);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
if (!hasGeometry) {
|
|
153
|
+
// Fallback: return a zero-size box at the object's world position
|
|
154
|
+
var position = new THREE__namespace.Vector3();
|
|
155
|
+
object.getWorldPosition(position);
|
|
156
|
+
box.setFromCenterAndSize(position, new THREE__namespace.Vector3(0, 0, 0));
|
|
157
|
+
console.warn("[boundingBoxUtils] computeFilteredBoundingBox: No geometry found for ".concat(object.uuid, ", returning empty box"));
|
|
158
|
+
}
|
|
159
|
+
return box;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Computes individual world-space bounding boxes for each io-device child
|
|
164
|
+
* of a component. Uses standard THREE.Box3.setFromObject() on each io-device
|
|
165
|
+
* since io-devices don't have their own sub-devices that need filtering.
|
|
166
|
+
*
|
|
167
|
+
* @param {THREE.Object3D} componentObject - The component's Three.js object
|
|
168
|
+
* @returns {Array<{uuid: string, userData: Object, worldBoundingBox: {min: number[], max: number[]}}>}
|
|
169
|
+
* Array of io-device bounding box descriptors ready for injection into scene data
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* const ioDeviceBBoxes = computeIODeviceBoundingBoxes(pumpModel)
|
|
173
|
+
* // Returns: [{ uuid: 'signal-light-1', userData: {...}, worldBoundingBox: { min: [...], max: [...] } }]
|
|
174
|
+
*/
|
|
175
|
+
function computeIODeviceBoundingBoxes(componentObject) {
|
|
176
|
+
var results = [];
|
|
177
|
+
var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(componentObject.children),
|
|
178
|
+
_step;
|
|
179
|
+
try {
|
|
180
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
181
|
+
var _child$userData;
|
|
182
|
+
var child = _step.value;
|
|
183
|
+
if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') continue;
|
|
184
|
+
var bbox = new THREE__namespace.Box3().setFromObject(child);
|
|
185
|
+
if (!bbox.isEmpty()) {
|
|
186
|
+
results.push({
|
|
187
|
+
uuid: child.uuid,
|
|
188
|
+
userData: {
|
|
189
|
+
objectType: 'io-device',
|
|
190
|
+
deviceId: child.userData.deviceId || null,
|
|
191
|
+
attachmentId: child.userData.attachmentId || null,
|
|
192
|
+
parentComponentId: child.userData.parentComponentId || componentObject.uuid
|
|
193
|
+
},
|
|
194
|
+
worldBoundingBox: {
|
|
195
|
+
min: [bbox.min.x, bbox.min.y, bbox.min.z],
|
|
196
|
+
max: [bbox.max.x, bbox.max.y, bbox.max.z]
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (err) {
|
|
202
|
+
_iterator.e(err);
|
|
203
|
+
} finally {
|
|
204
|
+
_iterator.f();
|
|
205
|
+
}
|
|
206
|
+
return results;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Creates bounding box helpers for a selected object. For smart components
|
|
211
|
+
* (components with io-device children), this produces:
|
|
212
|
+
* - One filtered helper for the component body (excluding io-devices and connectors)
|
|
213
|
+
* - One helper per io-device child
|
|
214
|
+
*
|
|
215
|
+
* For non-smart components and other objects, produces a single standard BoxHelper.
|
|
216
|
+
*
|
|
217
|
+
* Each helper is tagged with metadata in userData for update tracking:
|
|
218
|
+
* - `isBoundingBox: true`
|
|
219
|
+
* - `sourceObjectUuid: string` — the object this helper represents
|
|
220
|
+
* - `isFiltered: boolean` — whether filtered computation was used
|
|
221
|
+
* - `excludeTypes: string[]` — types excluded (for recomputation)
|
|
222
|
+
*
|
|
223
|
+
* @param {THREE.Object3D} object - The selected scene object
|
|
224
|
+
* @param {number} color - Line color (hex), default green
|
|
225
|
+
* @returns {THREE.LineSegments[]} Array of box helpers to add to the scene
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* const helpers = createSelectionBoxHelpers(pumpModel, 0x00ff00)
|
|
229
|
+
* helpers.forEach(h => scene.add(h))
|
|
230
|
+
*/
|
|
231
|
+
function createSelectionBoxHelpers(object) {
|
|
232
|
+
var _object$children;
|
|
233
|
+
var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0x00ff00;
|
|
234
|
+
var helpers = [];
|
|
235
|
+
var excludeTypes = ['io-device', 'connector'];
|
|
236
|
+
|
|
237
|
+
// Check if this object has io-device children (smart component)
|
|
238
|
+
var hasIODevices = (_object$children = object.children) === null || _object$children === void 0 ? void 0 : _object$children.some(function (child) {
|
|
239
|
+
var _child$userData2;
|
|
240
|
+
return ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'io-device';
|
|
241
|
+
});
|
|
242
|
+
if (hasIODevices) {
|
|
243
|
+
// 1. Create filtered helper for the component body
|
|
244
|
+
var filteredBox = computeFilteredBoundingBox(object, excludeTypes);
|
|
245
|
+
var componentHelper = _createBoxHelperFromBox3(filteredBox, color);
|
|
246
|
+
componentHelper.isHelper = true;
|
|
247
|
+
componentHelper.userData = {
|
|
248
|
+
isBoundingBox: true,
|
|
249
|
+
sourceObjectUuid: object.uuid,
|
|
250
|
+
isFiltered: true,
|
|
251
|
+
excludeTypes: excludeTypes
|
|
252
|
+
};
|
|
253
|
+
helpers.push(componentHelper);
|
|
254
|
+
|
|
255
|
+
// 2. Create individual helpers for each io-device
|
|
256
|
+
var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(object.children),
|
|
257
|
+
_step2;
|
|
258
|
+
try {
|
|
259
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
260
|
+
var _child$userData3;
|
|
261
|
+
var child = _step2.value;
|
|
262
|
+
if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) !== 'io-device') continue;
|
|
263
|
+
var deviceBox = new THREE__namespace.Box3().setFromObject(child);
|
|
264
|
+
if (deviceBox.isEmpty()) continue;
|
|
265
|
+
var deviceHelper = _createBoxHelperFromBox3(deviceBox, color);
|
|
266
|
+
deviceHelper.isHelper = true;
|
|
267
|
+
deviceHelper.userData = {
|
|
268
|
+
isBoundingBox: true,
|
|
269
|
+
sourceObjectUuid: child.uuid,
|
|
270
|
+
isFiltered: false,
|
|
271
|
+
isIODevice: true,
|
|
272
|
+
parentComponentUuid: object.uuid
|
|
273
|
+
};
|
|
274
|
+
helpers.push(deviceHelper);
|
|
275
|
+
}
|
|
276
|
+
} catch (err) {
|
|
277
|
+
_iterator2.e(err);
|
|
278
|
+
} finally {
|
|
279
|
+
_iterator2.f();
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
// Standard BoxHelper for non-smart objects
|
|
283
|
+
var boxHelper = new THREE__namespace.BoxHelper(object, color);
|
|
284
|
+
boxHelper.isHelper = true;
|
|
285
|
+
boxHelper.userData = {
|
|
286
|
+
isBoundingBox: true,
|
|
287
|
+
sourceObjectUuid: object.uuid,
|
|
288
|
+
isFiltered: false
|
|
289
|
+
};
|
|
290
|
+
helpers.push(boxHelper);
|
|
291
|
+
}
|
|
292
|
+
return helpers;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Updates a set of bounding box helpers to reflect current object transforms.
|
|
297
|
+
* Handles both standard BoxHelpers and filtered/io-device helpers.
|
|
298
|
+
*
|
|
299
|
+
* @param {THREE.LineSegments[]} helpers - Array of box helpers
|
|
300
|
+
* @param {THREE.Object3D[]} selectedObjects - The selected scene objects
|
|
301
|
+
* @param {THREE.Scene} scene - The scene (for finding objects by uuid)
|
|
302
|
+
*/
|
|
303
|
+
function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
|
|
304
|
+
var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(helpers),
|
|
305
|
+
_step3;
|
|
306
|
+
try {
|
|
307
|
+
var _loop = function _loop() {
|
|
308
|
+
var helper = _step3.value;
|
|
309
|
+
var _helper$userData = helper.userData,
|
|
310
|
+
sourceObjectUuid = _helper$userData.sourceObjectUuid,
|
|
311
|
+
isFiltered = _helper$userData.isFiltered,
|
|
312
|
+
excludeTypes = _helper$userData.excludeTypes,
|
|
313
|
+
isIODevice = _helper$userData.isIODevice,
|
|
314
|
+
parentComponentUuid = _helper$userData.parentComponentUuid;
|
|
315
|
+
var sourceObject;
|
|
316
|
+
if (isIODevice && parentComponentUuid) {
|
|
317
|
+
var _parent$children;
|
|
318
|
+
// Find the parent component first, then the io-device child
|
|
319
|
+
var parent = scene.getObjectByProperty('uuid', parentComponentUuid);
|
|
320
|
+
sourceObject = parent === null || parent === void 0 || (_parent$children = parent.children) === null || _parent$children === void 0 ? void 0 : _parent$children.find(function (c) {
|
|
321
|
+
return c.uuid === sourceObjectUuid;
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
sourceObject = selectedObjects.find(function (obj) {
|
|
325
|
+
return obj.uuid === sourceObjectUuid;
|
|
326
|
+
}) || scene.getObjectByProperty('uuid', sourceObjectUuid);
|
|
327
|
+
}
|
|
328
|
+
if (!sourceObject) return 1; // continue
|
|
329
|
+
sourceObject.updateMatrixWorld(true);
|
|
330
|
+
if (isFiltered && excludeTypes) {
|
|
331
|
+
// Recompute filtered bbox
|
|
332
|
+
var box = computeFilteredBoundingBox(sourceObject, excludeTypes);
|
|
333
|
+
_updateBoxHelperPositions(helper, box);
|
|
334
|
+
} else if (isIODevice) {
|
|
335
|
+
// Recompute io-device bbox
|
|
336
|
+
var _box = new THREE__namespace.Box3().setFromObject(sourceObject);
|
|
337
|
+
_updateBoxHelperPositions(helper, _box);
|
|
338
|
+
} else if (helper.update) {
|
|
339
|
+
// Standard BoxHelper — use built-in update
|
|
340
|
+
helper.update();
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
344
|
+
if (_loop()) continue;
|
|
345
|
+
}
|
|
346
|
+
} catch (err) {
|
|
347
|
+
_iterator3.e(err);
|
|
348
|
+
} finally {
|
|
349
|
+
_iterator3.f();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
exports.computeFilteredBoundingBox = computeFilteredBoundingBox;
|
|
354
|
+
exports.computeIODeviceBoundingBoxes = computeIODeviceBoundingBoxes;
|
|
355
|
+
exports.createSelectionBoxHelpers = createSelectionBoxHelpers;
|
|
356
|
+
exports.updateSelectionBoxHelpers = updateSelectionBoxHelpers;
|
|
@@ -15,7 +15,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
15
15
|
* Initialize the CentralPlant manager
|
|
16
16
|
*
|
|
17
17
|
* @constructor
|
|
18
|
-
* @version 0.1.
|
|
18
|
+
* @version 0.1.78
|
|
19
19
|
* @updated 2025-10-22
|
|
20
20
|
*
|
|
21
21
|
* @description Creates a new CentralPlant instance and initializes internal managers and utilities.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createClass as _createClass, objectSpread2 as _objectSpread2, createForOfIteratorHelper as _createForOfIteratorHelper, typeof as _typeof, classCallCheck as _classCallCheck, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
1
|
+
import { createClass as _createClass, objectSpread2 as _objectSpread2, createForOfIteratorHelper as _createForOfIteratorHelper, typeof as _typeof, slicedToArray as _slicedToArray, classCallCheck as _classCallCheck, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../_virtual/_rollupPluginBabelHelpers.js';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import { CentralPlantValidator } from './centralPlantValidator.js';
|
|
4
4
|
import { createTransformControls } from '../managers/controls/transformControlsManager.js';
|
|
@@ -1031,6 +1031,11 @@ var CentralPlantInternals = /*#__PURE__*/function () {
|
|
|
1031
1031
|
});
|
|
1032
1032
|
}
|
|
1033
1033
|
|
|
1034
|
+
// Add attached IO device models for smart components
|
|
1035
|
+
if (componentData.isSmart && componentData.attachedDevices) {
|
|
1036
|
+
this._attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1034
1039
|
// Notify the component manager about the new component
|
|
1035
1040
|
if (componentManager.registerComponent) {
|
|
1036
1041
|
componentManager.registerComponent(componentModel);
|
|
@@ -1081,6 +1086,74 @@ var CentralPlantInternals = /*#__PURE__*/function () {
|
|
|
1081
1086
|
}
|
|
1082
1087
|
}
|
|
1083
1088
|
|
|
1089
|
+
/**
|
|
1090
|
+
* Attach IO device models to a smart component from cached models.
|
|
1091
|
+
* Each device referenced in componentData.attachedDevices is looked up
|
|
1092
|
+
* in the model preloader cache, cloned, positioned, and added as a child.
|
|
1093
|
+
* @param {THREE.Object3D} componentModel - The parent component model
|
|
1094
|
+
* @param {Object} componentData - Component dictionary entry (has attachedDevices)
|
|
1095
|
+
* @param {Object} modelPreloader - ModelPreloader instance
|
|
1096
|
+
* @param {string} parentComponentId - The parent component's UUID
|
|
1097
|
+
* @private
|
|
1098
|
+
*/
|
|
1099
|
+
}, {
|
|
1100
|
+
key: "_attachIODevicesToComponent",
|
|
1101
|
+
value: function _attachIODevicesToComponent(componentModel, componentData, modelPreloader, parentComponentId) {
|
|
1102
|
+
var attachedDevices = componentData.attachedDevices;
|
|
1103
|
+
console.log("\uD83D\uDD0C addComponent(): Attaching ".concat(Object.keys(attachedDevices).length, " IO devices to smart component"));
|
|
1104
|
+
for (var _i = 0, _Object$entries = Object.entries(attachedDevices); _i < _Object$entries.length; _i++) {
|
|
1105
|
+
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
|
|
1106
|
+
attachmentId = _Object$entries$_i[0],
|
|
1107
|
+
attachment = _Object$entries$_i[1];
|
|
1108
|
+
try {
|
|
1109
|
+
var _modelPreloader$compo, _attachment$attachmen;
|
|
1110
|
+
var deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
|
|
1111
|
+
if (!deviceData || !deviceData.modelKey) {
|
|
1112
|
+
console.warn("\u26A0\uFE0F IO device ".concat(attachment.deviceId, " not found in dictionary, skipping"));
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
var deviceModel = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
|
|
1116
|
+
if (!deviceModel) {
|
|
1117
|
+
console.warn("\u26A0\uFE0F IO device model not in cache: ".concat(deviceData.modelKey, ", skipping"));
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Name the device model
|
|
1122
|
+
deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
|
|
1123
|
+
|
|
1124
|
+
// Set user data for identification
|
|
1125
|
+
deviceModel.userData = {
|
|
1126
|
+
objectType: 'io-device',
|
|
1127
|
+
deviceId: attachment.deviceId,
|
|
1128
|
+
attachmentId: attachmentId,
|
|
1129
|
+
attachmentLabel: attachment.attachmentLabel,
|
|
1130
|
+
parentComponentId: parentComponentId
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
// Position at the attachment point
|
|
1134
|
+
if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
|
|
1135
|
+
var pos = attachment.attachmentPoint.position;
|
|
1136
|
+
deviceModel.position.set(pos.x || 0, pos.y || 0, pos.z || 0);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// IO device models are authored at the same real-world unit scale
|
|
1140
|
+
// as the host component, so keep them at their natural (1:1) size.
|
|
1141
|
+
// Note: attachmentPoint.scale is the connector marker sphere size,
|
|
1142
|
+
// NOT a desired device model scale.
|
|
1143
|
+
deviceModel.scale.setScalar(1);
|
|
1144
|
+
|
|
1145
|
+
// Add as child of the component
|
|
1146
|
+
componentModel.add(deviceModel);
|
|
1147
|
+
console.log("\u2705 Attached IO device: ".concat(attachment.attachmentLabel || attachment.deviceId, " at"), {
|
|
1148
|
+
position: deviceModel.position,
|
|
1149
|
+
scale: deviceModel.scale
|
|
1150
|
+
});
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
console.error("\u274C Error attaching IO device ".concat(attachment.deviceId, ":"), err);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1084
1157
|
/**
|
|
1085
1158
|
* Delete a component from the scene by componentId (internal implementation)
|
|
1086
1159
|
* @param {string} componentId - The UUID of the component to delete
|