PageScene: collapse Editor facades and relocate scene-object methods

Drops the remaining 1-line forwarders on Editor and rewires their
callers to talk to the owning subsystem directly.

Inlined at call sites then deleted:
- updateOutliner → selection.refreshOutliner (3 actions/* callers)
- publishSelect → selection.publishSelect (1 action + helper.ts)
- updateSelectedUI → selection.updateSelectedUI (in-engine callers)
- cacheScene → save_manager.getSceneJson (EngineProvider)
- setColor → activeScene.setColor (ControlPanelSceneObject)
- isObjectLipsync deleted entirely (zero callers)

Relocated:
- lockUnlockObject + isObjectLocked moved onto SelectionBridge. The
  bridge gains a SelectionBridgeDeps surface for the userData mutation
  (still in SceneUtils as toggleObjectLocked) and the gizmo
  attach/detach. Callers (Outliner, ControlPanelSceneObject) now go
  through editor.selection.
- setSelectedObject (which despite the name wrote position/rotation/
  scale onto the active selection) becomes
  SceneManager.updateSelectedTransform — same data ownership, no more
  reverse dependency from helper.ts into sceneManager.selected_objects.

helper.ts shrinks to removeTransformControls, isObjectLocked,
toggleObjectLocked, getSelectedSum, deleteObject. The pre-existing
TS errors in deleteObject (Mesh.texture / Material[] dispose) are
untouched — out of scope for this refactor.
This commit is contained in:
Tanish Manku committed 2026-04-30 23:55:27 -07:00
1 parent 5ec73e362d
commit bccdd8df5b
11 files changed
+73 -126

No files matched your search

@@ -23,6 +23,6 @@ export async function addCharacter(
name: obj.name || (item.name ?? "character"),
mediaId: item.media_id,
});
editor.updateOutliner?.();
editor.selection.refreshOutliner();
return obj.uuid;
}
@@ -21,6 +21,6 @@ export async function addObject(
name: obj.name || (item.name ?? "object"),
mediaId: item.media_id,
});
editor.updateOutliner?.();
editor.selection.refreshOutliner();
return obj.uuid;
}
@@ -21,6 +21,6 @@ export async function addShape(
name: obj.name || (item.name ?? "shape"),
mediaId: item.media_id,
});
editor.updateOutliner?.();
editor.selection.refreshOutliner();
return obj.uuid;
}
@@ -11,5 +11,5 @@ export function deselectObject(editor: Editor): void {
if (editor.sceneManager) {
editor.sceneManager.selected_objects = undefined;
}
editor.publishSelect();
editor.selection.publishSelect();
}
@@ -84,7 +84,7 @@ export const ControlPanelSceneObject = () => {
}
setInputsUpdated(false);
editorEngine.setSelectedObject(
editorEngine.sceneManager?.updateSelectedTransform(
localToEngine(localPosition),
localToEngine(localRotation),
localToEngine(localScale),
@@ -103,7 +103,7 @@ export const ControlPanelSceneObject = () => {
setLocalRotation(engineToLocal(vectors.rotation));
setLocalScale(engineToLocal(vectors.scale));
setLocked(editorEngine.isObjectLocked(editorEngine?.selected?.uuid || ""));
setLocked(editorEngine.selection.isObjectLocked(editorEngine?.selected?.uuid || ""));
setColor(editorEngine?.selected?.userData.color);
}, [currentSceneObject, editorEngine]);
@@ -220,7 +220,7 @@ export const ControlPanelSceneObject = () => {
className="h-0 w-0 cursor-pointer opacity-0"
id={colorInputId}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
editorEngine?.setColor(
editorEngine?.activeScene.setColor(
editorEngine?.selected?.uuid || "",
e.target.value,
);
@@ -48,7 +48,7 @@ const OutlinerRow = ({ item }: { item: OutlinerItem }) => {
const handleToggleLock = (e: React.MouseEvent) => {
e.stopPropagation();
usePageSceneStore.getState().toggleOutlinerLock(item.id);
editorEngine?.lockUnlockObject(item.id);
editorEngine?.selection.lockUnlockObject(item.id);
};
const handleToggleVisibility = (e: React.MouseEvent) => {
@@ -59,9 +59,7 @@ export const EngineProvider = ({ sceneToken, children }: Props) => {
const sceneGenerationMetadata = getSceneGenerationMetaData(
activeEditorRef.current,
);
const cacheJson = activeEditorRef.current.cacheScene({
sceneTitle: "",
sceneToken: "",
const cacheJson = activeEditorRef.current.save_manager.getSceneJson({
sceneGenerationMetadata: sceneGenerationMetadata,
});
const cacheString = JSON.stringify(cacheJson);
@@ -4,7 +4,6 @@ import Scene from "./scene.js";
import { APIManager } from "./api_manager.js";
import { PointerLockControls } from "three/addons/controls/PointerLockControls.js";
import { CameraAspectRatio } from "~/pages/PageScene/enums";
import { XYZ } from "../datastructures/common";
import { SceneUtils } from "./helper";
import { MouseControls } from "./keybinds_controls";
import { SaveManager } from "./save_manager";
@@ -158,6 +157,15 @@ class Editor {
getSceneManager: () => this.sceneManager,
cameraName: this.cameraController.camera_name,
version: this.version,
toggleObjectLocked: (uuid) => this.utils.toggleObjectLocked(uuid),
isObjectLocked: (uuid) => this.utils.isObjectLocked(uuid),
removeTransformControls: () =>
this.utils.removeTransformControls(false),
attachGizmoToCurrentSelection: () => {
this.gizmo.addToScene(this.activeScene.scene);
const selected = this.sceneManager?.selected_objects?.[0];
if (selected) this.gizmo.attach(selected);
},
});
this.activeScene = new Scene(
@@ -634,29 +642,6 @@ class Editor {
this.selection.refreshOutliner();
}
isObjectLipsync(object_uuid: string) {
return this.utils.isObjectLipsync(object_uuid);
}
isObjectLocked(object_uuid: string): boolean {
return this.utils.isObjectLocked(object_uuid);
}
lockUnlockObject(object_uuid: string): boolean {
const res = this.utils.lockUnlockObject(object_uuid);
this.selection.updateSelectedUI();
return res;
}
setColor(object_uuid: string, hex_color: string) {
this.activeScene.setColor(object_uuid, hex_color);
}
// TO UPDATE selected objects in the scene might want to add to the scene ...
async setSelectedObject(position: XYZ, rotation: XYZ, scale: XYZ) {
this.utils.setSelectedObject(position, rotation, scale);
}
public async saveScene({
sceneTitle,
sceneToken,
@@ -673,20 +658,6 @@ class Editor {
});
}
public cacheScene({
sceneTitle,
sceneToken,
sceneGenerationMetadata,
}: {
sceneTitle: string;
sceneToken: string;
sceneGenerationMetadata: SceneGenereationMetaData;
}) {
return this.save_manager.getSceneJson({
sceneGenerationMetadata: sceneGenerationMetadata,
});
}
deleteObject(uuid: string) {
this.mouse_controls?.clearFKVisuals();
this.mouse_controls?.removeTransformControls(true);
@@ -826,20 +797,6 @@ class Editor {
setIs3DEditorInitialized(false);
console.log("3D Editor Engine unmounted");
}
// Facades — external callers in actions/* and helper.ts target these
// names. Internals reach the bridge directly via this.selection.*.
updateOutliner() {
this.selection.updateOutliner();
}
updateSelectedUI() {
this.selection.updateSelectedUI();
}
publishSelect() {
this.selection.publishSelect();
}
}
export default Editor;
@@ -13,7 +13,7 @@ import { AssetType, ClipGroup } from "~/enums";
import { usePageSceneStore } from "../../PageSceneStore";
import type { SceneManager } from "../scene_manager_api";
export type SelectionEngineRefs = {
export type SelectionBridgeDeps = {
// Lazy: SceneManager is created in Editor.initialize, after the bridge.
getSceneManager: () => SceneManager | undefined;
// The reserved camera-entity name ("::CAM::") used to distinguish
@@ -21,6 +21,14 @@ export type SelectionEngineRefs = {
cameraName: string;
// Editor's data version, written into the object panel.
version: number;
// Lock/unlock plumbing — the userData mutation lives in SceneUtils,
// and the gizmo attach/detach lives on GizmoController. The bridge
// calls them through these callbacks instead of importing either.
toggleObjectLocked: (uuid: string) => boolean; // returns new locked state
isObjectLocked: (uuid: string) => boolean;
removeTransformControls: () => void;
attachGizmoToCurrentSelection: () => void;
};
export class SelectionBridge {
@@ -32,7 +40,26 @@ export class SelectionBridge {
// compared in renderSingleFrame against utils.getSelectedSum().
last_selected_sum: number = 0;
constructor(private readonly engine: SelectionEngineRefs) {}
constructor(private readonly engine: SelectionBridgeDeps) {}
// Toggle the locked flag on an object's userData, then either yank
// the gizmo (lock) or re-attach it to the current selection (unlock).
// Returns the new locked state.
lockUnlockObject(object_uuid: string): boolean {
const locked = this.engine.toggleObjectLocked(object_uuid);
if (locked) {
this.engine.removeTransformControls();
} else {
this.engine.attachGizmoToCurrentSelection();
}
this.updateSelectedUI();
return locked;
}
// True if the object is currently locked. Pure passthrough.
isObjectLocked(object_uuid: string): boolean {
return this.engine.isObjectLocked(object_uuid);
}
setSelected(object: THREE.Object3D[] | undefined) {
const sceneManager = this.engine.getSceneManager();
@@ -1,7 +1,6 @@
import Scene from "./scene";
import Editor from "./editor";
import * as THREE from "three";
import { XYZ } from "../datastructures/common";
import { usePageSceneStore } from "../PageSceneStore";
export class SceneUtils {
@@ -19,26 +18,6 @@ export class SceneUtils {
);
}
// For react to see if an object has lipsync capability or not.
isObjectLipsync(object_uuid: string) {
const object = this.scene.get_object_by_uuid(object_uuid);
let hasLipsync = false;
if (object) {
object.traverse((c: THREE.Object3D) => {
if (c instanceof THREE.Mesh) {
if (c.morphTargetInfluences && c.morphTargetDictionary) {
const blendShapeIndexE = c.morphTargetDictionary["E"];
// console.log(c.morphTargetDictionary, blendShapeIndexE)
if (blendShapeIndexE !== null) {
hasLipsync = true;
}
}
}
});
}
return hasLipsync;
}
// Returns if the object is locked or unlocked.
isObjectLocked(object_uuid: string): boolean {
const object = this.scene.get_object_by_uuid(object_uuid);
@@ -48,31 +27,20 @@ export class SceneUtils {
}
return object.userData["locked"];
}
//console.log("No object found.");
return false;
}
// Locks or unlocks and object and returns its new state,
lockUnlockObject(object_uuid: string): boolean {
// Pure userData mutation: flips the locked flag and returns the new
// value. Higher-level wiring (gizmo attach/detach, selection refresh)
// lives on SelectionBridge.lockUnlockObject.
toggleObjectLocked(object_uuid: string): boolean {
const object = this.scene.get_object_by_uuid(object_uuid);
if (object) {
if (object.userData["locked"] == undefined) {
object.userData["locked"] = false;
}
object.userData["locked"] = !object.userData["locked"];
if (object.userData["locked"]) {
this.removeTransformControls(false);
} else {
this.editor.gizmo.addToScene(this.scene.scene);
const selected = this.editor.sceneManager?.selected_objects?.[0];
if (selected) this.editor.gizmo.attach(selected);
}
return object.userData["locked"];
if (!object) return false;
if (object.userData["locked"] == undefined) {
object.userData["locked"] = false;
}
//console.log("No object found.");
return false;
object.userData["locked"] = !object.userData["locked"];
return object.userData["locked"];
}
// Removes transform controls and publishes selected.
@@ -86,31 +54,13 @@ export class SceneUtils {
}
if (remove_outline) {
outlinePass.selectedObjects = [];
this.editor.publishSelect();
this.editor.selection.publishSelect();
}
this.editor.gizmo.detach();
this.editor.gizmo.removeFromScene(this.editor.activeScene.scene);
if (remove_outline) outlinePass.selectedObjects = [];
}
// TO UPDATE selected objects in the scene might want to add to the scene ...
async setSelectedObject(position: XYZ, rotation: XYZ, scale: XYZ) {
if (this.editor.sceneManager?.selected_objects) {
const object = this.editor.sceneManager?.selected_objects[0];
if (object != undefined || object != null) {
object.position.x = position.x;
object.position.y = position.y;
object.position.z = position.z;
object.rotation.x = THREE.MathUtils.degToRad(rotation.x);
object.rotation.y = THREE.MathUtils.degToRad(rotation.y);
object.rotation.z = THREE.MathUtils.degToRad(rotation.z);
object.scale.x = scale.x;
object.scale.y = scale.y;
object.scale.z = scale.z;
}
}
}
// Returns the "check sum" of the editors selected object.
getSelectedSum(): number {
if (this.editor.sceneManager?.selected_objects === undefined) {
@@ -206,7 +156,7 @@ function removeObject3D(object3D) {
usePageSceneStore.getState().removeSceneObject(uuid);
this.editor.selection.selected = undefined;
this.editor.publishSelect();
this.editor.selection.publishSelect();
usePageSceneStore.getState().hideObjectPanel();
}
}
@@ -9,6 +9,7 @@ import Scene from "./scene";
import { MouseControls } from "./keybinds_controls";
import { ClipGroup } from "~/enums";
import { AssetType } from "~/enums";
import { XYZ } from "../datastructures/common";
import { usePageSceneStore } from "~/pages/PageScene/PageSceneStore";
import {
CommandInputTypes,
@@ -257,6 +258,20 @@ export class SceneManager implements SceneManagerAPI {
}
}
// Writes position/rotation/scale onto the currently selected object.
// Rotation is taken in degrees (matches the Object Panel input).
public updateSelectedTransform(position: XYZ, rotation: XYZ, scale: XYZ) {
const object = this.selected_objects?.[0];
if (!object) return;
object.position.set(position.x, position.y, position.z);
object.rotation.set(
THREE.MathUtils.degToRad(rotation.x),
THREE.MathUtils.degToRad(rotation.y),
THREE.MathUtils.degToRad(rotation.z),
);
object.scale.set(scale.x, scale.y, scale.z);
}
// Converts a 3d object to signal item format.
private convert_object(
object: THREE.Object3D,