NODEDC_BIM_VIEWER/frontend/transformGizmo.js

165 lines
5.8 KiB
JavaScript

// Минимальный плагин трансформаций: хранит выбранный объект, даёт доступ к 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);
}
}
}