// Минимальный плагин трансформаций: хранит выбранный объект, даёт доступ к pos/rot/scale, // реагирует на клики по дереву. Трансформации применяются на уровне SceneModel. export class TransformGizmo { constructor({ viewer, sdk, treeView, transformStore, onTargetChange, onTransformChange, sceneModels }) { this.viewer = viewer; this.scene = viewer.scene; this.sdk = sdk; this.treeView = treeView; this.transformStore = transformStore || {}; this.sceneModels = sceneModels || {}; this.onTargetChange = onTargetChange; this.onTransformChange = onTransformChange; this.mode = "translate"; // translate | rotate | scale this.target = null; // { modelId, objectId } this._bindTree(); } _isPointCloudTarget(model, objectId = null) { const entity = objectId ? this.scene.objects[objectId] : null; const firstObject = entity || Object.values(model?.objects || {})[0]; const firstMesh = firstObject?.meshes?.[0] || model?.meshes?.[0]; return firstMesh?._geometry?._state?.primitiveName === "points"; } setMode(next) { if (!["translate", "rotate", "scale"].includes(next)) return; this.mode = next; this._log(`Режим гизмы: ${next}`); } clearTarget() { if (this.target?.objectId) { this.scene.setObjectsHighlighted([this.target.objectId], false); } this.scene.setObjectsHighlighted(this.scene.highlightedObjectIds || [], false); this.target = null; this.onTargetChange?.(null); this._log("Выбор очищен"); } setTarget({ modelId, objectId }) { if (!modelId) return; this.scene.setObjectsHighlighted(this.scene.highlightedObjectIds || [], false); const model = this.sceneModels ? this.sceneModels[modelId] || this.scene.models[modelId] : this.scene.models[modelId]; if (!model) { this._log(`Модель ${modelId} не найдена`, true); return; } // Если указан объект if (objectId) { const entity = this.scene.objects[objectId]; if (!entity) { this._log(`Объект ${objectId} не найден`, true); } else { const saved = this.transformStore?.[modelId]?.[objectId]; if (saved) { if (saved.position) model.position = saved.position; if (saved.rotation) model.rotation = saved.rotation; if (saved.scale) model.scale = saved.scale; } this.onTargetChange?.({ modelId, objectId, transform: { position: [...model.position], rotation: [...model.rotation], scale: model.scale ? [...model.scale] : [1, 1, 1], }, }); if (!this._isPointCloudTarget(model, objectId)) { this.scene.setObjectsHighlighted([objectId], true); } this.target = { modelId, objectId }; return; } } // Если objectId нет, работаем всей моделью const ids = Object.keys(model.objects || {}); if (ids.length && !this._isPointCloudTarget(model)) { this.scene.setObjectsHighlighted(ids, true); } this.onTargetChange?.({ modelId, objectId: null, transform: { position: [...model.position], rotation: [...model.rotation], scale: model.scale ? [...model.scale] : [1, 1, 1], }, }); this.target = { modelId, objectId: null }; } handleTreeNodeClick(evt) { const node = evt?.treeViewNode || evt?.node || evt; const objectId = node?.objectId || node?.id || node?.object?.id || node?.entityId || node?.objectIdGiven; const modelId = node?.modelId || node?.model?.id || node?.modelID || node?.model?.modelId || node?.modelIdGiven; if (objectId && modelId) { this.setTarget({ modelId, objectId }); } else if (modelId) { // нет objectId - подсветим всю модель const model = this.scene.models[modelId]; if (model) { const ids = Object.keys(model.objects || {}); this.scene.setObjectsHighlighted(this.scene.highlightedObjectIds || [], false); if (!this._isPointCloudTarget(model)) { this.scene.setObjectsHighlighted(ids, true); } this.target = { modelId, objectId: null }; } } } updateTransform({ position, rotation, scale }) { if (!this.target?.modelId) return; const model = this.sceneModels ? this.sceneModels[this.target.modelId] || this.scene.models[this.target.modelId] : this.scene.models[this.target.modelId]; if (!model) return; if (position) model.position = position; if (rotation) model.rotation = rotation; if (scale) model.scale = scale; // сохраняем в store if (!this.transformStore[this.target.modelId]) { this.transformStore[this.target.modelId] = {}; } const key = this.target.objectId || "__model__"; this.transformStore[this.target.modelId][key] = { position: [...model.position], rotation: [...model.rotation], scale: model.scale ? [...model.scale] : [1, 1, 1], }; this.onTransformChange?.({ modelId: this.target.modelId, objectId: this.target.objectId, transform: { position: [...model.position], rotation: [...model.rotation], scale: model.scale ? [...model.scale] : [1, 1, 1], }, }); } _bindTree() { if (this.treeView?.on) { this.treeView.on("nodeTitleClicked", (e) => this.handleTreeNodeClick(e)); // подстрахуемся: некоторые деревья бросают другой эвент this.treeView.on("nodeClicked", (e) => this.handleTreeNodeClick(e)); } } _log(msg, isError = false) { if (isError) { console.error("[TransformGizmo]", msg); } else { console.log("[TransformGizmo]", msg); } } }