@2112-lab/central-plant 0.1.77 → 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 +438 -43
- package/dist/cjs/src/core/centralPlant.js +1 -1
- 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/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
|
@@ -7,6 +7,7 @@ var THREE = require('three');
|
|
|
7
7
|
var pathfinder = require('@2112-lab/pathfinder');
|
|
8
8
|
var baseDisposable = require('../../core/baseDisposable.js');
|
|
9
9
|
var pathfindingData = require('../../core/pathfindingData.js');
|
|
10
|
+
var boundingBoxUtils = require('../../utils/boundingBoxUtils.js');
|
|
10
11
|
var sceneDataManager = require('./sceneDataManager.js');
|
|
11
12
|
var PathRenderingManager = require('./PathRenderingManager.js');
|
|
12
13
|
var ConnectorManager = require('./ConnectorManager.js');
|
|
@@ -177,20 +178,63 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
177
178
|
// Find the actual component object in the scene
|
|
178
179
|
var componentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
|
|
179
180
|
if (componentObject) {
|
|
180
|
-
// Compute
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
// Compute FILTERED bounding box — excludes io-device and connector subtrees
|
|
182
|
+
// so the component body bbox is tight-fitting and doesn't envelop attached devices
|
|
183
|
+
var filteredBBox = boundingBoxUtils.computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
|
|
184
|
+
console.log("\uD83D\uDD04 Updated worldBoundingBox for component ".concat(child.uuid, " (filtered): min=[").concat(filteredBBox.min.x.toFixed(2), ", ").concat(filteredBBox.min.y.toFixed(2), ", ").concat(filteredBBox.min.z.toFixed(2), "], max=[").concat(filteredBBox.max.x.toFixed(2), ", ").concat(filteredBBox.max.y.toFixed(2), ", ").concat(filteredBBox.max.z.toFixed(2), "]"));
|
|
183
185
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, child), {}, {
|
|
186
|
+
// Build the enriched component entry
|
|
187
|
+
var enrichedChild = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, child), {}, {
|
|
187
188
|
userData: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, child.userData), {}, {
|
|
188
189
|
worldBoundingBox: {
|
|
189
|
-
min: [
|
|
190
|
-
max: [
|
|
190
|
+
min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
|
|
191
|
+
max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
|
|
191
192
|
}
|
|
192
193
|
})
|
|
193
194
|
});
|
|
195
|
+
|
|
196
|
+
// Compute separate bounding boxes for each attached io-device
|
|
197
|
+
// These are injected as children so the pathfinder treats each as an independent obstacle
|
|
198
|
+
var ioDeviceBBoxes = boundingBoxUtils.computeIODeviceBoundingBoxes(componentObject);
|
|
199
|
+
if (ioDeviceBBoxes.length > 0) {
|
|
200
|
+
// Ensure children array exists (may already contain connectors)
|
|
201
|
+
if (!enrichedChild.children) {
|
|
202
|
+
enrichedChild.children = [];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Inject io-device entries with their own worldBoundingBox
|
|
206
|
+
ioDeviceBBoxes.forEach(function (deviceBBox) {
|
|
207
|
+
// Check if this io-device already exists in scene data children
|
|
208
|
+
var existingIndex = enrichedChild.children.findIndex(function (c) {
|
|
209
|
+
return c.uuid === deviceBBox.uuid;
|
|
210
|
+
});
|
|
211
|
+
if (existingIndex >= 0) {
|
|
212
|
+
// Update existing entry with bounding box
|
|
213
|
+
enrichedChild.children[existingIndex] = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
|
|
214
|
+
userData: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
|
|
215
|
+
objectType: 'io-device',
|
|
216
|
+
worldBoundingBox: deviceBBox.worldBoundingBox
|
|
217
|
+
})
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
// Create new entry for the io-device
|
|
221
|
+
enrichedChild.children.push({
|
|
222
|
+
uuid: deviceBBox.uuid,
|
|
223
|
+
userData: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, deviceBBox.userData), {}, {
|
|
224
|
+
worldBoundingBox: deviceBBox.worldBoundingBox
|
|
225
|
+
}),
|
|
226
|
+
children: []
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
console.log("\uD83D\uDCE6 Injected io-device bbox for ".concat(deviceBBox.uuid, ": min=[").concat(deviceBBox.worldBoundingBox.min.map(function (v) {
|
|
230
|
+
return v.toFixed(2);
|
|
231
|
+
}).join(', '), "], max=[").concat(deviceBBox.worldBoundingBox.max.map(function (v) {
|
|
232
|
+
return v.toFixed(2);
|
|
233
|
+
}).join(', '), "]"));
|
|
234
|
+
});
|
|
235
|
+
console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bounding box(es) for component ").concat(child.uuid));
|
|
236
|
+
}
|
|
237
|
+
return enrichedChild;
|
|
194
238
|
} else {
|
|
195
239
|
console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
|
|
196
240
|
}
|
|
@@ -7,6 +7,7 @@ var THREE = require('three');
|
|
|
7
7
|
var textureConfig = require('../environment/textureConfig.js');
|
|
8
8
|
var modelManager = require('./modelManager.js');
|
|
9
9
|
var sceneClearingUtility = require('../../utils/sceneClearingUtility.js');
|
|
10
|
+
var boundingBoxUtils = require('../../utils/boundingBoxUtils.js');
|
|
10
11
|
|
|
11
12
|
function _interopNamespace(e) {
|
|
12
13
|
if (e && e.__esModule) return e;
|
|
@@ -642,6 +643,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
|
|
|
642
643
|
|
|
643
644
|
/**
|
|
644
645
|
* Helper function to compute world bounding boxes
|
|
646
|
+
* For components: uses filtered bbox (excludes io-device and connector subtrees)
|
|
647
|
+
* For io-devices: computes separate bounding boxes and injects them as children
|
|
645
648
|
*/
|
|
646
649
|
}, {
|
|
647
650
|
key: "computeWorldBoundingBoxes",
|
|
@@ -680,12 +683,46 @@ var SceneOperationsManager = /*#__PURE__*/function () {
|
|
|
680
683
|
};
|
|
681
684
|
jsonObject = _findJsonObject(data.scene.children);
|
|
682
685
|
if (jsonObject) {
|
|
683
|
-
// Compute world bounding box
|
|
684
|
-
var boundingBox = new THREE__namespace.Box3().setFromObject(object);
|
|
685
|
-
|
|
686
686
|
// Store in JSON userData for pathfinder (skip for gateways - they're just routing points)
|
|
687
687
|
if (!jsonObject.userData) jsonObject.userData = {};
|
|
688
|
-
if (jsonObject.userData.objectType
|
|
688
|
+
if (jsonObject.userData.objectType === 'component') {
|
|
689
|
+
// For components: compute filtered bounding box (excludes io-device and connector subtrees)
|
|
690
|
+
var filteredBBox = boundingBoxUtils.computeFilteredBoundingBox(object, ['io-device', 'connector']);
|
|
691
|
+
jsonObject.userData.worldBoundingBox = {
|
|
692
|
+
min: filteredBBox.min.toArray(),
|
|
693
|
+
max: filteredBBox.max.toArray()
|
|
694
|
+
};
|
|
695
|
+
console.log("Added filtered world bounding box for component:", jsonObject.userData.worldBoundingBox);
|
|
696
|
+
|
|
697
|
+
// Compute and inject separate io-device bounding boxes as children
|
|
698
|
+
var ioDeviceBBoxes = boundingBoxUtils.computeIODeviceBoundingBoxes(object);
|
|
699
|
+
if (ioDeviceBBoxes.length > 0) {
|
|
700
|
+
if (!jsonObject.children) jsonObject.children = [];
|
|
701
|
+
ioDeviceBBoxes.forEach(function (deviceBBox) {
|
|
702
|
+
var existingIndex = jsonObject.children.findIndex(function (c) {
|
|
703
|
+
return c.uuid === deviceBBox.uuid;
|
|
704
|
+
});
|
|
705
|
+
if (existingIndex >= 0) {
|
|
706
|
+
// Update existing entry
|
|
707
|
+
if (!jsonObject.children[existingIndex].userData) jsonObject.children[existingIndex].userData = {};
|
|
708
|
+
jsonObject.children[existingIndex].userData.objectType = 'io-device';
|
|
709
|
+
jsonObject.children[existingIndex].userData.worldBoundingBox = deviceBBox.worldBoundingBox;
|
|
710
|
+
} else {
|
|
711
|
+
// Create new entry
|
|
712
|
+
jsonObject.children.push({
|
|
713
|
+
uuid: deviceBBox.uuid,
|
|
714
|
+
userData: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, deviceBBox.userData), {}, {
|
|
715
|
+
worldBoundingBox: deviceBBox.worldBoundingBox
|
|
716
|
+
}),
|
|
717
|
+
children: []
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bbox(es) for component ").concat(jsonObject.uuid));
|
|
722
|
+
}
|
|
723
|
+
} else if (jsonObject.userData.objectType !== 'gateway') {
|
|
724
|
+
// For non-component, non-gateway objects: standard bounding box
|
|
725
|
+
var boundingBox = new THREE__namespace.Box3().setFromObject(object);
|
|
689
726
|
jsonObject.userData.worldBoundingBox = {
|
|
690
727
|
min: boundingBox.min.toArray(),
|
|
691
728
|
max: boundingBox.max.toArray()
|
|
@@ -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.
|
|
@@ -2,6 +2,7 @@ import { createClass as _createClass, asyncToGenerator as _asyncToGenerator, reg
|
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import { transformControls } from './transformControls.js';
|
|
4
4
|
import { isSegment, isGateway } from '../../utils/objectTypes.js';
|
|
5
|
+
import { createSelectionBoxHelpers, updateSelectionBoxHelpers } from '../../utils/boundingBoxUtils.js';
|
|
5
6
|
|
|
6
7
|
var TransformControlsManager = /*#__PURE__*/function () {
|
|
7
8
|
function TransformControlsManager(scene, camera, renderer) {
|
|
@@ -918,23 +919,16 @@ var TransformControlsManager = /*#__PURE__*/function () {
|
|
|
918
919
|
return;
|
|
919
920
|
}
|
|
920
921
|
try {
|
|
921
|
-
// Create
|
|
922
|
+
// Create bounding box helpers for each selected object
|
|
923
|
+
// Smart components get filtered helpers (component body + individual io-device boxes)
|
|
922
924
|
this.selectedObjects.forEach(function (obj) {
|
|
923
|
-
var
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
isBoundingBox: true
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
// Add to scene
|
|
932
|
-
_this5.scene.add(boundingBoxHelper);
|
|
933
|
-
|
|
934
|
-
// Store in array for later cleanup
|
|
935
|
-
_this5.boundingBoxHelpers.push(boundingBoxHelper);
|
|
925
|
+
var helpers = createSelectionBoxHelpers(obj, _this5.config.boundingBoxColor);
|
|
926
|
+
helpers.forEach(function (helper) {
|
|
927
|
+
_this5.scene.add(helper);
|
|
928
|
+
_this5.boundingBoxHelpers.push(helper);
|
|
929
|
+
});
|
|
936
930
|
});
|
|
937
|
-
console.log("\uD83D\uDCE6 Bounding boxes created for ".concat(this.selectedObjects.length, " object(s)"));
|
|
931
|
+
console.log("\uD83D\uDCE6 Bounding boxes created for ".concat(this.selectedObjects.length, " object(s) (").concat(this.boundingBoxHelpers.length, " helpers)"));
|
|
938
932
|
} catch (error) {
|
|
939
933
|
console.warn('⚠️ Failed to create bounding boxes:', error);
|
|
940
934
|
}
|
|
@@ -1065,21 +1059,19 @@ var TransformControlsManager = /*#__PURE__*/function () {
|
|
|
1065
1059
|
// Update bounding boxes for all selected objects
|
|
1066
1060
|
if (this.selectedObjects.length > 0 && this.boundingBoxHelpers.length > 0) {
|
|
1067
1061
|
try {
|
|
1068
|
-
//
|
|
1069
|
-
this.
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
_this6.boundingBoxCache.set(obj, updatedBoundingBox);
|
|
1082
|
-
}
|
|
1062
|
+
// Ensure all selected objects have up-to-date matrices
|
|
1063
|
+
this.selectedObjects.forEach(function (obj) {
|
|
1064
|
+
return obj.updateMatrixWorld(true);
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
// Use the centralized update function which handles filtered, io-device, and standard helpers
|
|
1068
|
+
updateSelectionBoxHelpers(this.boundingBoxHelpers, this.selectedObjects, this.scene);
|
|
1069
|
+
|
|
1070
|
+
// Also update the cached bounding box if it exists
|
|
1071
|
+
this.selectedObjects.forEach(function (obj) {
|
|
1072
|
+
if (_this6.boundingBoxCache.has(obj)) {
|
|
1073
|
+
var updatedBoundingBox = new THREE.Box3().setFromObject(obj);
|
|
1074
|
+
_this6.boundingBoxCache.set(obj, updatedBoundingBox);
|
|
1083
1075
|
}
|
|
1084
1076
|
});
|
|
1085
1077
|
} catch (error) {
|
|
@@ -3,6 +3,7 @@ import * as THREE from 'three';
|
|
|
3
3
|
import { Pathfinder } from '@2112-lab/pathfinder';
|
|
4
4
|
import { BaseDisposable } from '../../core/baseDisposable.js';
|
|
5
5
|
import { PathData } from '../../core/pathfindingData.js';
|
|
6
|
+
import { computeFilteredBoundingBox, computeIODeviceBoundingBoxes } from '../../utils/boundingBoxUtils.js';
|
|
6
7
|
import { SceneDataManager } from './sceneDataManager.js';
|
|
7
8
|
import { PathRenderingManager } from './PathRenderingManager.js';
|
|
8
9
|
import { ConnectorManager } from './ConnectorManager.js';
|
|
@@ -153,20 +154,63 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
153
154
|
// Find the actual component object in the scene
|
|
154
155
|
var componentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
|
|
155
156
|
if (componentObject) {
|
|
156
|
-
// Compute
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
// Compute FILTERED bounding box — excludes io-device and connector subtrees
|
|
158
|
+
// so the component body bbox is tight-fitting and doesn't envelop attached devices
|
|
159
|
+
var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
|
|
160
|
+
console.log("\uD83D\uDD04 Updated worldBoundingBox for component ".concat(child.uuid, " (filtered): min=[").concat(filteredBBox.min.x.toFixed(2), ", ").concat(filteredBBox.min.y.toFixed(2), ", ").concat(filteredBBox.min.z.toFixed(2), "], max=[").concat(filteredBBox.max.x.toFixed(2), ", ").concat(filteredBBox.max.y.toFixed(2), ", ").concat(filteredBBox.max.z.toFixed(2), "]"));
|
|
159
161
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
return _objectSpread2(_objectSpread2({}, child), {}, {
|
|
162
|
+
// Build the enriched component entry
|
|
163
|
+
var enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
|
|
163
164
|
userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
|
|
164
165
|
worldBoundingBox: {
|
|
165
|
-
min: [
|
|
166
|
-
max: [
|
|
166
|
+
min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
|
|
167
|
+
max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
|
|
167
168
|
}
|
|
168
169
|
})
|
|
169
170
|
});
|
|
171
|
+
|
|
172
|
+
// Compute separate bounding boxes for each attached io-device
|
|
173
|
+
// These are injected as children so the pathfinder treats each as an independent obstacle
|
|
174
|
+
var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
|
|
175
|
+
if (ioDeviceBBoxes.length > 0) {
|
|
176
|
+
// Ensure children array exists (may already contain connectors)
|
|
177
|
+
if (!enrichedChild.children) {
|
|
178
|
+
enrichedChild.children = [];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Inject io-device entries with their own worldBoundingBox
|
|
182
|
+
ioDeviceBBoxes.forEach(function (deviceBBox) {
|
|
183
|
+
// Check if this io-device already exists in scene data children
|
|
184
|
+
var existingIndex = enrichedChild.children.findIndex(function (c) {
|
|
185
|
+
return c.uuid === deviceBBox.uuid;
|
|
186
|
+
});
|
|
187
|
+
if (existingIndex >= 0) {
|
|
188
|
+
// Update existing entry with bounding box
|
|
189
|
+
enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
|
|
190
|
+
userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
|
|
191
|
+
objectType: 'io-device',
|
|
192
|
+
worldBoundingBox: deviceBBox.worldBoundingBox
|
|
193
|
+
})
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
// Create new entry for the io-device
|
|
197
|
+
enrichedChild.children.push({
|
|
198
|
+
uuid: deviceBBox.uuid,
|
|
199
|
+
userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
|
|
200
|
+
worldBoundingBox: deviceBBox.worldBoundingBox
|
|
201
|
+
}),
|
|
202
|
+
children: []
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
console.log("\uD83D\uDCE6 Injected io-device bbox for ".concat(deviceBBox.uuid, ": min=[").concat(deviceBBox.worldBoundingBox.min.map(function (v) {
|
|
206
|
+
return v.toFixed(2);
|
|
207
|
+
}).join(', '), "], max=[").concat(deviceBBox.worldBoundingBox.max.map(function (v) {
|
|
208
|
+
return v.toFixed(2);
|
|
209
|
+
}).join(', '), "]"));
|
|
210
|
+
});
|
|
211
|
+
console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bounding box(es) for component ").concat(child.uuid));
|
|
212
|
+
}
|
|
213
|
+
return enrichedChild;
|
|
170
214
|
} else {
|
|
171
215
|
console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
|
|
172
216
|
}
|