From 7f3a79afbabb5ef2e1945f45a9104000c2c93a54 Mon Sep 17 00:00:00 2001 From: Laura Sach <5183697+lawsie@users.noreply.github.com> Date: Thu, 25 Jun 2026 08:21:55 +0100 Subject: [PATCH 1/4] HUD rotate on world axis --- ui/gizmos.js | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/ui/gizmos.js b/ui/gizmos.js index 8d459f49..93452dde 100644 --- a/ui/gizmos.js +++ b/ui/gizmos.js @@ -814,23 +814,42 @@ function startRotateKeyboardHandler(mesh, savedHudAxis = null, onHudAxisSaved = if (creationBlock) highlightBlockById(Blockly.getMainWorkspace(), creationBlock); } + // Track the rotation as Euler degrees (the block's own representation) rather + // than composing increments onto the quaternion and reading Euler back. A + // single-axis drag then changes only that axis's value, and the mesh is + // rebuilt with RotationYawPitchRoll — identical to what rotate_to applies — so + // the live view always matches the block. This also makes each axis a + // WORLD-axis rotation, like the drag arcs: rotating "Y" yaws a tilted mesh + // about the vertical, instead of spinning it about its own (local) axis, which + // on a shape symmetric about that axis (e.g. a capsule) looked like no change + // and smeared every Euler component across all three block values. + const working = (() => { + const e = getMeshRotationInDegrees(mesh); + return { x: e.x, y: e.y, z: e.z }; + })(); + const axisInput = { x: 'X', y: 'Y', z: 'Z' }; const onMove = (dx, dy, dz) => { - if (!mesh.rotationQuaternion) { - mesh.rotationQuaternion = flock.BABYLON.Quaternion.FromEulerAngles( - mesh.rotation.x, - mesh.rotation.y, - mesh.rotation.z - ); + const deltas = { x: dx, y: dy, z: dz }; + const changedAxes = []; + for (const axisKey of ['x', 'y', 'z']) { + if (deltas[axisKey]) { + working[axisKey] += flock.BABYLON.Tools.ToDegrees(deltas[axisKey]); + changedAxes.push(axisKey); + } } - const delta = flock.BABYLON.Quaternion.RotationYawPitchRoll(dy, dx, dz); - mesh.rotationQuaternion.multiplyInPlace(delta).normalize(); + mesh.rotationQuaternion = flock.BABYLON.Quaternion.RotationYawPitchRoll( + flock.BABYLON.Tools.ToRadians(working.y), + flock.BABYLON.Tools.ToRadians(working.x), + flock.BABYLON.Tools.ToRadians(working.z) + ); if (isBodyAlive(mesh.physics)) { mesh.physics.disablePreStep = false; mesh.physics.setTargetTransform(mesh.absolutePosition, mesh.rotationQuaternion); } if (rotateBlock && !rotateBlock.disposed) { - const rot = getMeshRotationInDegrees(mesh); - setBlockXYZ(rotateBlock, rot.x, rot.y, rot.z); + for (const axisKey of changedAxes) { + setBlockAxisValue(rotateBlock, axisInput[axisKey], working[axisKey]); + } } }; const onConfirm = () => { From 5c4dc2a6fcd73b8045ecf9d50db6b6918276973a Mon Sep 17 00:00:00 2001 From: Laura Sach <5183697+lawsie@users.noreply.github.com> Date: Thu, 25 Jun 2026 08:25:54 +0100 Subject: [PATCH 2/4] Linter issue --- ui/gizmos.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/gizmos.js b/ui/gizmos.js index 93452dde..22c2d96b 100644 --- a/ui/gizmos.js +++ b/ui/gizmos.js @@ -949,7 +949,7 @@ function setBlockAxisValue(block, inputName, value) { } // Find an existing rotate_to block in mesh's DO section without creating one. -function findExistingRotateBlock(mesh) { +function _findExistingRotateBlock(mesh) { const block = meshMap[mesh?.metadata?.blockKey]; if (!block) return null; const modelVariable = block.getFieldValue('ID_VAR'); @@ -1531,7 +1531,7 @@ export function toggleGizmo(gizmoType) { gizmoManager.boundingBoxGizmoEnabled = true; break; case "bounds": - handleBoundsGizmo(); + _handleBoundsGizmo(); break; */ case 'focus': @@ -1900,7 +1900,7 @@ function handlePositionGizmo() { // Bounds: Allow the user to move the mesh // Legacy? -function handleBoundsGizmo() { +function _handleBoundsGizmo() { gizmoManager.boundingBoxGizmoEnabled = true; gizmoManager.boundingBoxDragBehavior.onDragStartObservable.add(function () { const mesh = gizmoManager.attachedMesh; From d2c5ac5c953784cdd4f5a5d467c810b1b235c37f Mon Sep 17 00:00:00 2001 From: Laura Sach <5183697+lawsie@users.noreply.github.com> Date: Thu, 25 Jun 2026 08:33:12 +0100 Subject: [PATCH 3/4] Uniform restrictions --- ui/axis-keyboard.js | 3 +++ ui/gizmos.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/axis-keyboard.js b/ui/axis-keyboard.js index d65779b7..053a1f95 100644 --- a/ui/axis-keyboard.js +++ b/ui/axis-keyboard.js @@ -12,6 +12,7 @@ export function createAxisKeyboardHandler({ stepFast = 1, onAxisChange, initialAxis = null, + allowUniform = false, }) { let axis = initialAxis; @@ -72,6 +73,8 @@ export function createAxisKeyboardHandler({ case "u": case "U": + // Uniform (all-axes) only applies to scale; ignore on move/rotate. + if (!allowUniform) break; axis = axis === "all" ? null : "all"; flock.printText({ text: axis ? `🔒 ★ ${translate("axis_all")}` : translate("axis_free"), diff --git a/ui/gizmos.js b/ui/gizmos.js index 22c2d96b..3ed6a83f 100644 --- a/ui/gizmos.js +++ b/ui/gizmos.js @@ -100,7 +100,7 @@ function createAdaptiveInput({ onMove, onConfirm, onCancel, stepNormal, stepFast } hud = createGizmoMobileHud({ onMove, stepNormal, stepFast, mode, showUniform, stepLabels, onAxisChange: onHudAxisChange, stepLabelsByAxis, initialAxis: initialHudAxis ?? initialKeyboardAxis }); - keyboard = createAxisKeyboardHandler({ onMove, onConfirm, onCancel, stepNormal, stepFast, onAxisChange: onKbAxisChange, initialAxis: initialKeyboardAxis }); + keyboard = createAxisKeyboardHandler({ onMove, onConfirm, onCancel, stepNormal, stepFast, onAxisChange: onKbAxisChange, initialAxis: initialKeyboardAxis, allowUniform: showUniform }); const startAxis = initialKeyboardAxis ?? initialHudAxis; if (startAxis) onAxisChange?.(startAxis); flock.canvas?.focus(); From 3a68c17e549813cb473f0c1ad7338ff9149650dd Mon Sep 17 00:00:00 2001 From: Laura Sach <5183697+lawsie@users.noreply.github.com> Date: Thu, 25 Jun 2026 08:44:50 +0100 Subject: [PATCH 4/4] Normalise axis --- ui/axis-keyboard.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/axis-keyboard.js b/ui/axis-keyboard.js index 053a1f95..3e80ed09 100644 --- a/ui/axis-keyboard.js +++ b/ui/axis-keyboard.js @@ -14,7 +14,11 @@ export function createAxisKeyboardHandler({ initialAxis = null, allowUniform = false, }) { - let axis = initialAxis; + // "all" (uniform) is only valid when uniform mode is enabled. In non-uniform + // tools, collapse any inherited/incoming "all" (e.g. carried over from the + // scale tool) to a single axis so Arrow/Page movement stays axis-constrained. + const normalizeAxis = (a) => (a === "all" && !allowUniform ? "x" : a); + let axis = normalizeAxis(initialAxis); function handler(event) { const t = event.target; @@ -159,7 +163,7 @@ export function createAxisKeyboardHandler({ KeyboardDispatcher.popMode(); } stop.getAxis = () => axis; - stop.setAxis = (newAxis) => { axis = newAxis; }; + stop.setAxis = (newAxis) => { axis = normalizeAxis(newAxis); }; return stop; }