From 8c2b7f9c7d5891557266b78d741710655840704c Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Tue, 13 Jan 2026 11:16:43 -0600 Subject: [PATCH] Fix diagram text sync, resize handle positioning, and PouchDB delete handling - Fix diagramObject text setter to update entity before notifying observers - Improve ResizeGizmo handle positioning directly at corners/faces with constant screen-size scaling - Fix PouchDB sync to handle deleted documents using _id field for compatibility Co-Authored-By: Claude Opus 4.5 --- package.json | 2 +- src/diagram/diagramObject.ts | 15 ++++++-- src/gizmos/ResizeGizmo/ResizeGizmo.ts | 51 +++++++++++++-------------- src/integration/database/pouchData.ts | 11 ++++-- 4 files changed, 47 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index ed59a48..9e10078 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "immersive", "private": true, - "version": "0.0.8-45", + "version": "0.0.8-46", "type": "module", "license": "MIT", "engines": { diff --git a/src/diagram/diagramObject.ts b/src/diagram/diagramObject.ts index 7500bac..3426ad2 100644 --- a/src/diagram/diagramObject.ts +++ b/src/diagram/diagramObject.ts @@ -227,13 +227,24 @@ export class DiagramObject { if (this._labelBack) { this._labelBack.dispose(); } - if (this._diagramEntity.text != value) { + const textChanged = this._diagramEntity.text != value; + + // Update the entity text FIRST (before notifying observers) + this._diagramEntity.text = value; + + // Also update mesh metadata to keep in sync with diagramEntity getter + if (this._mesh && this._mesh.metadata) { + this._mesh.metadata.text = value; + } + + // THEN notify observers with the UPDATED entity + if (textChanged) { this._eventObservable.notifyObservers({ type: DiagramEventType.MODIFY, entity: this._diagramEntity }, DiagramEventObserverMask.TO_DB); } - this._diagramEntity.text = value; + this._label = createLabel(value); this._label.parent = this._baseTransform; this._labelBack = new InstancedMesh('labelBack' + value, (this._label as Mesh)); diff --git a/src/gizmos/ResizeGizmo/ResizeGizmo.ts b/src/gizmos/ResizeGizmo/ResizeGizmo.ts index 14cd4e2..a14c92f 100644 --- a/src/gizmos/ResizeGizmo/ResizeGizmo.ts +++ b/src/gizmos/ResizeGizmo/ResizeGizmo.ts @@ -108,6 +108,10 @@ export class ResizeGizmo { * Create corner and face handles */ private createHandles(): void { + // Ensure world matrix and bounding info are current + this._targetMesh.computeWorldMatrix(true); + this._targetMesh.refreshBoundingInfo(); + // Get bounding box for positioning const targetBoundingInfo = this._targetMesh.getBoundingInfo(); const boundingBox = targetBoundingInfo.boundingBox; @@ -116,10 +120,10 @@ export class ResizeGizmo { const innerCorners = boundingBox.vectorsWorld; const worldMatrix = this._targetMesh.getWorldMatrix(); - // Calculate handle size once (based on corner distance) - const handleSize = innerCorners[0].subtract(bboxCenter).length() * .5; + // Unit size - actual visual size controlled by updateHandleScaling() + const handleSize = 1.0; - // Create corner handles + // Create corner handles - positioned directly at bounding box corners CORNER_POSITIONS.forEach((cornerDef, index) => { const cornerPos = innerCorners[index]; @@ -129,10 +133,8 @@ export class ResizeGizmo { this._utilityLayer.utilityLayerScene ); - // Position outward from center so handle corner touches bounding box corner - const direction = cornerPos.subtract(bboxCenter).normalize(); - const offset = direction.scale(handleSize * Math.sqrt(3) / 2); - handleMesh.position = cornerPos.add(offset); + // Position handle at the corner (scaling will make it small) + handleMesh.position = cornerPos.clone(); handleMesh.rotationQuaternion = this._targetMesh.absoluteRotationQuaternion; handleMesh.material = this._handleMaterial; handleMesh.isPickable = true; @@ -156,10 +158,8 @@ export class ResizeGizmo { this._utilityLayer.utilityLayerScene ); - // Position outward from center so handle touches face center - const direction = faceCenterWorld.subtract(bboxCenter).normalize(); - const offset = direction.scale(handleSize * Math.sqrt(3) / 2); - handleMesh.position = faceCenterWorld.add(offset); + // Position handle at the face center (scaling will make it small) + handleMesh.position = faceCenterWorld.clone(); handleMesh.rotationQuaternion = this._targetMesh.absoluteRotationQuaternion; handleMesh.material = this._handleMaterial; handleMesh.isPickable = true; @@ -197,15 +197,20 @@ export class ResizeGizmo { } /** - * Update handle scaling based on camera distance for consistent visual size + * Update handle scaling based on camera distance for constant screen size. + * Handles will appear the same visual size regardless of distance. */ private updateHandleScaling(): void { const camera = this._scene.activeCamera; if (!camera) return; + // Target angular size - tune this for desired visual size + // 0.03 means handles appear ~3cm at 1m distance, ~6cm at 2m, etc. + const targetAngularSize = 0.03; + for (const handle of this._handles) { const distance = Vector3.Distance(camera.globalPosition, handle.position); - const scaleFactor = distance; // Adjust multiplier to control visual size + const scaleFactor = distance * targetAngularSize; handle.scaling = new Vector3(scaleFactor, scaleFactor, scaleFactor); } } @@ -482,27 +487,24 @@ export class ResizeGizmo { * Update handle positions and sizes to match current target mesh bounding box */ private updateHandleTransforms(): void { + // Ensure world matrix and bounding info are current + this._targetMesh.computeWorldMatrix(true); + this._targetMesh.refreshBoundingInfo(); + const targetBoundingInfo = this._targetMesh.getBoundingInfo(); const boundingBox = targetBoundingInfo.boundingBox; - const bboxCenter = boundingBox.centerWorld; const extents = boundingBox.extendSize; const innerCorners = boundingBox.vectorsWorld; const worldMatrix = this._targetMesh.getWorldMatrix(); - // Recalculate handle size based on new bounding box - const newHandleSize = innerCorners[0].subtract(bboxCenter).length() * .2; - let handleIndex = 0; - // Update corner handles (first 8 handles) + // Update corner handles (first 8 handles) - position at corners for (let i = 0; i < CORNER_POSITIONS.length; i++) { const handle = this._handles[handleIndex]; const cornerPos = innerCorners[i]; - // Update position - const direction = cornerPos.subtract(bboxCenter).normalize(); - const offset = direction.scale(newHandleSize * Math.sqrt(3) / 2); - handle.position = cornerPos.add(offset); + handle.position = cornerPos.clone(); handle.rotationQuaternion = this._targetMesh.absoluteRotationQuaternion; handleIndex++; @@ -520,10 +522,7 @@ export class ResizeGizmo { ); const faceCenterWorld = Vector3.TransformCoordinates(localFacePos, worldMatrix); - // Update position - const direction = faceCenterWorld.subtract(bboxCenter).normalize(); - const offset = direction.scale(newHandleSize * Math.sqrt(3) / 2); - handle.position = faceCenterWorld.add(offset); + handle.position = faceCenterWorld.clone(); handle.rotationQuaternion = this._targetMesh.absoluteRotationQuaternion; handleIndex++; diff --git a/src/integration/database/pouchData.ts b/src/integration/database/pouchData.ts index 511976f..a0dbd5c 100644 --- a/src/integration/database/pouchData.ts +++ b/src/integration/database/pouchData.ts @@ -61,9 +61,14 @@ export class PouchData { if (info.direction === 'pull' && info.change && info.change.docs) { info.change.docs.forEach((doc) => { if (doc._deleted) { - this.onDBEntityRemoveObservable.notifyObservers(doc); - } else if (doc.id && doc.id !== 'metadata') { - this.onDBEntityUpdateObservable.notifyObservers(doc); + // PouchDB deleted documents only have _id, not our custom id field + // Create an entity with id from _id for the remove handler + const entity = { id: doc._id, ...doc }; + this.onDBEntityRemoveObservable.notifyObservers(entity); + } else if ((doc.id || doc._id) && (doc.id || doc._id) !== 'metadata') { + // Accept both id and _id for compatibility + const entity = doc.id ? doc : { id: doc._id, ...doc }; + this.onDBEntityUpdateObservable.notifyObservers(entity); } }); }