Refactored diagram manager and loggers.

This commit is contained in:
Michael Mainguy 2023-07-28 09:30:10 -05:00
parent fc588c5dbe
commit d87d8bace4
14 changed files with 325 additions and 138 deletions

View File

@ -25,7 +25,8 @@ import {DiagramManager} from "./diagram/diagramManager";
import {Toolbox} from "./toolbox/toolbox";
import {DualshockEventMapper} from "./util/dualshockEventMapper";
import log from "loglevel";
import {AppConfig} from "./util/appConfig";
import {IndexdbPersistenceManager} from "./diagram/indexdbPersistenceManager";
export class App {
//preTasks = [havokModule];
@ -35,6 +36,8 @@ export class App {
private rig: Rigplatform;
constructor() {
const config = AppConfig.config;
log.setLevel('debug');
const canvas = document.createElement("canvas");
canvas.style.width = "100%";
@ -56,10 +59,6 @@ export class App {
this.scene.dispose();
this.scene = null;
}
if (DiagramManager.onDiagramEventObservable) {
DiagramManager.onDiagramEventObservable.clear();
DiagramManager.onDiagramEventObservable = null;
}
const engine = new Engine(canvas, true);
const scene = new Scene(engine);
@ -106,10 +105,14 @@ export class App {
}
});
const persistenceManager = new IndexdbPersistenceManager("diagram");
const diagramManager = new DiagramManager(this.scene, this.xr.baseExperience);
this.rig = new Rigplatform(this.scene, this.xr);
const toolbox = new Toolbox(scene, this.xr.baseExperience);
diagramManager.setPersistenceManager(persistenceManager);
AppConfig.config.setPersistenceManager(persistenceManager);
this.rig = new Rigplatform(this.scene, this.xr, diagramManager);
const toolbox = new Toolbox(scene, this.xr.baseExperience, diagramManager);
this.scene.gamepadManager.onGamepadConnectedObservable.add((gamepad) => {
try {
@ -119,7 +122,7 @@ export class App {
const buttonEvent = DualshockEventMapper.mapButtonEvent(button, 1);
if (buttonEvent.objectName) {
window.dispatchEvent(new CustomEvent('pa-button-state-change', {
detail: buttonEvent
detail: buttonEvent
}
));
}
@ -187,7 +190,7 @@ export class App {
}
});
log.info('App', 'keydown event listener added, use Ctrl+Shift+Alt+I to toggle debug layer');
persistenceManager.initialize();
engine.runRenderLoop(() => {
scene.render();

View File

@ -1,6 +1,5 @@
import {
AbstractMesh,
Angle,
InstancedMesh,
Mesh,
Scene,
@ -14,7 +13,6 @@ import {DiagramManager} from "../diagram/diagramManager";
import {DiagramEvent, DiagramEventType} from "../diagram/diagramEntity";
import log from "loglevel";
import {AppConfig} from "../util/appConfig";
import round from "round";
export class Base {
@ -29,12 +27,16 @@ export class Base {
protected previousPosition: Vector3 = null;
protected readonly xr: WebXRDefaultExperience;
protected readonly diagramManager: DiagramManager;
constructor(controller: WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience) {
constructor(controller: WebXRInputSource,
scene: Scene,
xr: WebXRDefaultExperience,
diagramManager: DiagramManager) {
this.controller = controller;
this.scene = scene;
this.xr = xr;
this.diagramManager = diagramManager;
this.controller.onMotionControllerInitObservable.add((init) => {
if (init.components['xr-standard-trigger']) {
init.components['xr-standard-trigger']
@ -66,35 +68,6 @@ export class Base {
}
static snapRotation(rotation): Vector3 {
const config = AppConfig.config;
if (config.rotateSnap == 0) {
return rotation;
}
rotation.x = this.CalcToSnap(rotation.x, config.rotateSnap);
rotation.y = this.CalcToSnap(rotation.y, config.rotateSnap);
rotation.z = this.CalcToSnap(rotation.z, config.rotateSnap);
return rotation;
}
static snapPosition(position): Vector3 {
const config = AppConfig.config;
if (config.gridSnap == 0) {
return position;
}
position.x = round(position.x, config.gridSnap);
position.y = round(position.y, config.gridSnap);
position.z = round(position.z, config.gridSnap);
return position;
}
static CalcToSnap(val, snap) {
const deg = Angle.FromRadians(val).degrees();
const snappedDegrees = round(deg, snap);
log.getLogger('Base').debug("deg", val, deg, snappedDegrees, snap);
return Angle.FromDegrees(snappedDegrees).radians();
}
private initGrip(grip: WebXRControllerComponent) {
grip.onButtonStateChangedObservable.add(() => {
if (grip.changes.pressed) {
@ -124,10 +97,11 @@ export class Base {
mesh && mesh.setParent(this.controller.motionController.rootMesh);
this.grabbedMesh = mesh;
} else {
const config = AppConfig.config;
const newMesh = this.createCopy(mesh);
newMesh.position = mesh.absolutePosition.clone();
newMesh.rotation = mesh.absoluteRotationQuaternion.toEulerAngles().clone();
newMesh.scaling = mesh.absoluteScaling.clone();
newMesh.scaling = config.createSnapVal;
newMesh.material = mesh.material;
newMesh.metadata = mesh.metadata;
newMesh && newMesh.setParent(this.controller.motionController.rootMesh);
@ -156,15 +130,19 @@ export class Base {
return;
}
}
const snappedRotation = Base.snapRotation(mesh.absoluteRotationQuaternion.toEulerAngles().clone());
const snappedPosition = Base.snapPosition(mesh.absolutePosition.clone());
const config = AppConfig.config;
const snappedRotation = config.snapRotateVal(mesh.absoluteRotationQuaternion.toEulerAngles().clone());
const snappedPosition = config.snapGridVal(mesh.absolutePosition.clone());
if (this.previousParent) {
const p = this.scene.getMeshById(this.previousParent);
if (p) {
mesh && mesh.setParent(this.scene.getMeshById(this.previousParent));
log.getLogger("Base").warn("Base", "Have not implemented snapping to parent yet");
//@note: this is not implemented yet
} else {
mesh && mesh.setParent(null);
mesh.rotation = snappedRotation;
mesh.position = snappedPosition;
}
} else {
mesh && mesh.setParent(null);
@ -180,7 +158,7 @@ export class Base {
this.previousScaling = null;
this.previousRotation = null;
this.previousPosition = null;
DiagramManager.onDiagramEventObservable.notifyObservers(event);
this.diagramManager.onDiagramEventObservable.notifyObservers(event);
}
}
});

View File

@ -1,7 +1,8 @@
import {AbstractMesh, Observable, TransformNode} from "@babylonjs/core";
export type ControllerEventType = {
type: string
type: string,
value?: number
}
export class Controllers {
public static movable: TransformNode | AbstractMesh;

View File

@ -3,15 +3,17 @@ import {Base} from "./base";
import {Controllers} from "./controllers";
import log from "loglevel";
import {ConfigMenu} from "../menus/configMenu";
import {DiagramManager} from "../diagram/diagramManager";
export class Left extends Base {
public static instance: Left;
public configMenu: ConfigMenu;
constructor(controller:
WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience) {
super(controller, scene, xr);
constructor(controller:
WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience, diagramManager: DiagramManager) {
super(controller, scene, xr, diagramManager);
this.configMenu = new ConfigMenu(this.scene, xr.baseExperience);
Left.instance = this;
this.controller.onMotionControllerInitObservable.add((init) => {

View File

@ -2,13 +2,15 @@ import {Base} from "./base";
import {Scene, Vector3, WebXRControllerComponent, WebXRDefaultExperience, WebXRInputSource} from "@babylonjs/core";
import {Controllers} from "./controllers";
import log from "loglevel";
import {DiagramManager} from "../diagram/diagramManager";
export class Right extends Base {
public static instance: Right;
constructor(controller:
WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience) {
super(controller, scene, xr);
WebXRInputSource, scene: Scene, xr: WebXRDefaultExperience, diagramManager: DiagramManager
) {
super(controller, scene, xr, diagramManager);
Right.instance = this;
this.controller.onMotionControllerInitObservable.add((init) => {
this.initTrigger(init.components['xr-standard-trigger']);

View File

@ -19,6 +19,7 @@ import {Left} from "./left";
import {EditMenu} from "../menus/editMenu";
import {Controllers} from "./controllers";
import log from "loglevel";
import {DiagramManager} from "../diagram/diagramManager";
export class Rigplatform {
@ -34,13 +35,15 @@ export class Rigplatform {
private camera: Camera;
private turning: boolean = false;
private velocity: Vector3 = Vector3.Zero();
private readonly diagramManager: DiagramManager;
constructor(scene: Scene, xr: WebXRDefaultExperience) {
constructor(scene: Scene, xr: WebXRDefaultExperience, diagramManager: DiagramManager) {
this.scene = scene;
this.diagramManager = diagramManager;
Rigplatform.xr = xr;
Rigplatform.instance = this;
this.bMenu = new EditMenu(scene, xr.baseExperience);
this.bMenu = new EditMenu(scene, xr.baseExperience, this.diagramManager);
this.camera = scene.activeCamera;
this.rigMesh = MeshBuilder.CreateBox("platform", {width: 2, height: .02, depth: 2}, scene);
@ -111,11 +114,11 @@ export class Rigplatform {
let controller;
switch (source.inputSource.handedness) {
case "right":
Right.instance = new Right(source, this.scene, Rigplatform.xr);
Right.instance = new Right(source, this.scene, Rigplatform.xr, this.diagramManager);
Controllers.controllerObserver.add((event: { type: string, value: number }) => {
switch (event.type) {
case "increaseVelocity":
if (this.velocityIndex < this.velocityArray.length -1) {
if (this.velocityIndex < this.velocityArray.length - 1) {
this.velocityIndex++;
} else {
this.velocityIndex = 0;
@ -150,7 +153,7 @@ export class Rigplatform {
});
break;
case "left":
Left.instance = new Left(source, this.scene, Rigplatform.xr);
Left.instance = new Left(source, this.scene, Rigplatform.xr, this.diagramManager);
break;
}

View File

@ -1,41 +1,42 @@
import {Observable, Scene, WebXRExperienceHelper} from "@babylonjs/core";
import {DiagramEntity, DiagramEvent, DiagramEventType} from "./diagramEntity";
import {IPersistenceManager} from "./persistenceManager";
import {IndexdbPersistenceManager} from "./indexdbPersistenceManager";
import {MeshConverter} from "./meshConverter";
import log from "loglevel";
export class DiagramManager {
private persistenceManager: IPersistenceManager = new IndexdbPersistenceManager("diagram");
static onDiagramEventObservable: Observable<DiagramEvent> = new Observable();
public readonly onDiagramEventObservable: Observable<DiagramEvent> = new Observable();
private readonly logger = log.getLogger('DiagramManager');
private persistenceManager: IPersistenceManager = null;
private readonly scene: Scene;
private xr: WebXRExperienceHelper;
constructor(scene: Scene, xr: WebXRExperienceHelper) {
this.scene = scene;
this.xr = xr;
this.persistenceManager.updateObserver.add(this.onRemoteEvent, -1, true, this);
log.getLogger('DiagramManager').debug( "remote event observer added");
this.persistenceManager.initialize();
if (!DiagramManager.onDiagramEventObservable) {
log.getLogger('DiagramManager').debug( "onDiagramEventObservable missing, recreated");
DiagramManager.onDiagramEventObservable = new Observable();
if (this.onDiagramEventObservable.hasObservers()) {
this.logger.warn("onDiagramEventObservable already has Observers, you should be careful");
}
if (DiagramManager.onDiagramEventObservable.hasObservers()) {
log.getLogger('DiagramManager').debug("onDiagramEventObservable already has Observers, this shouldn't happen");
} else {
DiagramManager.onDiagramEventObservable.add(this.onDiagramEvent, -1, true, this);
log.getLogger('DiagramManager').debug( "onDiagramEventObservable Observer added");
}
log.getLogger('DiagramManager').debug( "DiagramManager constructed");
this.onDiagramEventObservable.add(this.onDiagramEvent, -1, true, this);
this.logger.debug("DiagramManager constructed");
}
public setPersistenceManager(persistenceManager: IPersistenceManager) {
this.persistenceManager = persistenceManager;
this.persistenceManager.updateObserver.add(this.onRemoteEvent, -1, true, this);
}
private getPersistenceManager(): IPersistenceManager {
if (!this.persistenceManager) {
this.logger.warn("persistenceManager not set");
return null;
}
return this.persistenceManager;
}
private onRemoteEvent(event: DiagramEntity) {
//const mesh = Toolbox.instance.newMesh(ToolType[Object.entries(ToolType).find(e => e[1] == event.template)[0]], event.id);
log.getLogger('DiagramManager').debug(event);
this.logger.debug(event);
const mesh = MeshConverter.fromDiagramEntity(event, this.scene);
if (event.parent) {
mesh.parent = this.scene.getMeshById(event.parent);
@ -43,7 +44,7 @@ export class DiagramManager {
}
private onDiagramEvent(event: DiagramEvent) {
log.getLogger("DiagramManager").debug(event);
this.logger.debug(event.type);
const entity = event.entity;
let mesh;
if (entity) {
@ -55,19 +56,19 @@ export class DiagramManager {
case DiagramEventType.DROPPED:
break;
case DiagramEventType.DROP:
this.persistenceManager.add(mesh);
this.getPersistenceManager()?.add(mesh)
break;
case DiagramEventType.ADD:
break;
case DiagramEventType.MODIFY:
this.persistenceManager.modify(mesh);
this.getPersistenceManager()?.modify(mesh)
break;
case DiagramEventType.CHANGECOLOR:
this.persistenceManager.changeColor(event.oldColor, event.newColor);
this.getPersistenceManager()?.changeColor(event.oldColor, event.newColor);
break;
case DiagramEventType.REMOVE:
if (mesh) {
this.persistenceManager.remove(mesh);
this.getPersistenceManager()?.remove(mesh)
mesh.dispose();
}
break;

View File

@ -6,16 +6,18 @@ import {MeshConverter} from "./meshConverter";
import log from "loglevel";
export class IndexdbPersistenceManager implements IPersistenceManager {
private readonly logger = log.getLogger('IndexdbPersistenceManager');
public updateObserver: Observable<DiagramEntity> = new Observable<DiagramEntity>();
private db: Dexie;
constructor(name: string) {
this.db = new Dexie(name);
this.db.version(1).stores({entities: "id,position,rotation,last_seen,template,text,scale,color"});
log.debug('IndexdbPersistenceManager', "IndexdbPersistenceManager constructed");
this.logger.debug("IndexdbPersistenceManager constructed");
}
public add(mesh: AbstractMesh) {
if (!mesh) {
log.warn('IndexdbPersistenceManager', "Adding null mesh");
this.logger.error("Adding null mesh, early return");
return;
}
const entity = <any>MeshConverter.toDiagramEntity(mesh);
@ -24,36 +26,54 @@ export class IndexdbPersistenceManager implements IPersistenceManager {
entity.scale = this.vectoxys(mesh.scaling);
this.db["entities"].add(entity);
}
private vectoxys(v: Vector3): {x, y ,z} {
return {x: v.x, y: v.y, z: v.z};
}
private xyztovec(xyz: {x, y, z}): Vector3 {
return new Vector3(xyz.x, xyz.y, xyz.z);
this.logger.debug('add', mesh, entity);
}
public remove(mesh: AbstractMesh) {
public remove(mesh: AbstractMesh) {
this.db["entities"].delete(mesh.id);
}
public modify(mesh) {
public getConfig(): any {
this.logger.warn('getConfig not implemented');
//@todo implement
}
public setConfig(config: any) {
this.logger.warn('setConfig not implemented, value not persisted', config);
//@todo implement
}
public modify(mesh) {
const entity = <any>MeshConverter.toDiagramEntity(mesh);
entity.position = this.vectoxys(mesh.position);
entity.rotation = this.vectoxys(mesh.rotation);
entity.scale = this.vectoxys(mesh.scaling);
this.db["entities"].update(mesh.id, entity);
this.logger.debug('modify', mesh, entity);
}
public initialize() {
this.logger.info('initialize', this.db['entities'].length);
this.db['entities'].each((e) => {
e.position = this.xyztovec(e.position);
e.rotation = this.xyztovec(e.rotation);
e.scale = this.xyztovec(e.scale);
log.debug('IndexdbPersistenceManager', 'adding', e);
this.logger.debug('adding', e);
this.updateObserver.notifyObservers(e);
});
log.warn('IndexdbPersistenceManager', "initialize finished");
this.logger.info("initialize finished");
}
public changeColor(oldColor, newColor) {
log.debug('IndexdbPersistenceManager', `changeColor ${oldColor.toHexString()} to ${newColor.toHexString()}`);
this.logger.debug(`changeColor ${oldColor.toHexString()} to ${newColor.toHexString()}`);
this.db['entities'].where('color').equals(oldColor.toHexString()).modify({color: newColor.toHexString()});
}
private vectoxys(v: Vector3): { x, y, z } {
return {x: v.x, y: v.y, z: v.z};
}
private xyztovec(xyz: { x, y, z }): Vector3 {
return new Vector3(xyz.x, xyz.y, xyz.z);
}
}

View File

@ -1,12 +1,22 @@
import {AbstractMesh, Color3, Observable} from "@babylonjs/core";
import {DiagramEntity} from "./diagramEntity";
import {AppConfigType} from "../util/appConfig";
export interface IPersistenceManager {
add(mesh: AbstractMesh);
remove(mesh: AbstractMesh);
modify(mesh: AbstractMesh);
initialize();
getConfig(): AppConfigType;
setConfig(config: AppConfigType);
changeColor(oldColor: Color3, newColor: Color3)
updateObserver: Observable<DiagramEntity>;
}

View File

@ -3,7 +3,7 @@ import {AdvancedDynamicTexture, InputText} from "@babylonjs/gui";
export class InputTextView {
private mesh: AbstractMesh;
private scene: Scene;
private readonly scene: Scene;
private xr: WebXRExperienceHelper;
private inputPlane: AbstractMesh;
private inputText: InputText;

View File

@ -38,30 +38,52 @@ export class ConfigMenu {
selectionPanel.fontSize = "24px";
selectionPanel.height = "100%";
configTexture.addControl(selectionPanel)
const radio1 = new RadioGroup("Rotation Snap");
radio1.addRadio("Off", this.rotateVal);
radio1.addRadio("22.5 degrees", this.rotateVal);
radio1.addRadio("45 degrees", this.rotateVal);
radio1.addRadio("90 degrees", this.rotateVal);
selectionPanel.addGroup(radio1);
const radio2 = new RadioGroup("Grid Snap");
radio2.addRadio("Off", this.gridVal);
radio2.addRadio("1 cm", this.gridVal);
radio2.addRadio("10 cm", this.gridVal);
radio2.addRadio("25 cm", this.gridVal);
selectionPanel.addGroup(radio1);
selectionPanel.addGroup(radio2);
selectionPanel.addGroup(this.buildGridSizeControl());
selectionPanel.addGroup(this.buildRotationSnapControl());
selectionPanel.addGroup(this.buildCreateScaleControl());
this.configPlane.position = CameraHelper.getFrontPosition(2, this.scene);
this.configPlane.rotation.y = Angle.FromDegrees(180).radians();
}
private createVal(value) {
AppConfig.config.currentCreateSnapIndex = value;
log.debug("configMenu", "create Snap", value);
}
private buildCreateScaleControl(): RadioGroup {
const radio = new RadioGroup("Create Scale");
for (const [index, snap] of AppConfig.config.createSnaps().entries()) {
const selected = AppConfig.config.currentCreateSnapIndex == index;
radio.addRadio(snap.label, this.createVal, selected);
}
return radio;
}
private buildRotationSnapControl(): RadioGroup {
const radio = new RadioGroup("Rotation Snap");
for (const [index, snap] of AppConfig.config.rotateSnaps().entries()) {
const selected = AppConfig.config.currentRotateSnapIndex == index;
radio.addRadio(snap.label, this.rotateVal, selected);
}
return radio;
}
private buildGridSizeControl(): RadioGroup {
const radio = new RadioGroup("Grid Snap");
for (const [index, snap] of AppConfig.config.gridSnaps().entries()) {
const selected = AppConfig.config.currentGridSnapIndex == index;
radio.addRadio(snap.label, this.gridVal, selected);
}
return radio;
}
private rotateVal(value) {
AppConfig.config.rotateSnap = AppConfig.config.rotateSnapArray[value];
AppConfig.config.currentRotateSnapIndex = value;
log.debug("configMenu", "rotate Snap", value);
}
private gridVal(value) {
AppConfig.config.gridSnap = AppConfig.config.gridSnapArray[value];
AppConfig.config.currentGridSnapIndex = value;
log.debug("configMenu", "grid Snap", value);
}

View File

@ -24,12 +24,13 @@ export class EditMenu {
private textView: InputTextView;
private textInput: HTMLElement;
private gizmoManager: GizmoManager;
private xr: WebXRExperienceHelper;
constructor(scene: Scene, xr: WebXRExperienceHelper) {
private readonly xr: WebXRExperienceHelper;
private readonly diagramManager: DiagramManager;
constructor(scene: Scene, xr: WebXRExperienceHelper, diagramManager: DiagramManager) {
this.scene = scene;
this.xr = xr;
this.diagramManager = diagramManager;
this.gizmoManager = new GizmoManager(scene);
this.gizmoManager.boundingBoxGizmoEnabled = true;
this.gizmoManager.gizmos.boundingBoxGizmo.scaleBoxSize = .020;
@ -71,7 +72,7 @@ export class EditMenu {
entity:
MeshConverter.toDiagramEntity(pointerInfo.pickInfo.pickedMesh)
}
DiagramManager.onDiagramEventObservable.notifyObservers(event);
this.diagramManager.onDiagramEventObservable.notifyObservers(event);
break;
case BmenuState.MODIFYING:
if (pointerInfo.pickInfo.pickedMesh.metadata?.template &&
@ -83,7 +84,7 @@ export class EditMenu {
this.gizmoManager.attachToMesh(mesh);
this.gizmoManager.gizmos.boundingBoxGizmo.onScaleBoxDragObservable.add(() => {
DiagramManager.onDiagramEventObservable.notifyObservers({
this.diagramManager.onDiagramEventObservable.notifyObservers({
type: DiagramEventType.MODIFY,
entity: MeshConverter.toDiagramEntity(mesh),
}
@ -158,7 +159,7 @@ export class EditMenu {
} else {
log.getLogger('bmenu').error("mesh has no metadata");
}
DiagramManager.onDiagramEventObservable.notifyObservers({
this.diagramManager.onDiagramEventObservable.notifyObservers({
type: DiagramEventType.MODIFY,
entity: MeshConverter.toDiagramEntity(mesh),
});

View File

@ -27,27 +27,30 @@ export enum ToolType {
}
export class Toolbox {
public static getToolTypeFromString(type: string): ToolType {
return ToolType[Object.keys(ToolType).find(() => type)]
}
private index = 0;
public static instance: Toolbox;
private readonly scene: Scene;
private readonly xr: WebXRExperienceHelper;
public readonly node : TransformNode;
public readonly node: TransformNode;
private readonly diagramManager: DiagramManager;
private readonly manager: GUI3DManager;
private readonly gridsize = 5;
private readonly addPanel: StackPanel3D;
constructor (scene:Scene, xr: WebXRExperienceHelper) {
constructor(scene: Scene, xr: WebXRExperienceHelper, diagramManager: DiagramManager) {
this.scene = scene;
this.diagramManager = diagramManager;
this.addPanel = new StackPanel3D();
this.manager = new GUI3DManager(scene);
this.manager = new GUI3DManager(scene);
this.manager.addControl(this.addPanel);
this.node = new TransformNode("toolbox", this.scene);
const handle = MeshBuilder.CreateCapsule("handle", { radius: .01 , orientation: Vector3.Right(), height: .3}, this.scene);
const handle = MeshBuilder.CreateCapsule("handle", {
radius: .01,
orientation: Vector3.Right(),
height: .3
}, this.scene);
handle.id = "handle";
const handleMaterial = new StandardMaterial("handle-material", this.scene);
const handleMaterial = new StandardMaterial("handle-material", this.scene);
handleMaterial.diffuseColor = Color3.FromHexString("#EEEEFF");
handle.material = handleMaterial;
handle.position = CameraHelper.getFrontPosition(2, this.scene);
@ -129,7 +132,7 @@ export class Toolbox {
material.name = "material-" + value.toHexString();
mesh.id = "toolbox-color-" + value.toHexString();
mesh.name = "toolbox-color-" + value.toHexString();
DiagramManager.onDiagramEventObservable.notifyObservers(
this.diagramManager.onDiagramEventObservable.notifyObservers(
{
type: DiagramEventType.CHANGECOLOR,
oldColor: oldColor,
@ -206,9 +209,6 @@ export class Toolbox {
return null;
}
}
public show() {
this.buildToolbox();
}
}
function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] {
return Object.keys(obj).filter(k => Number.isNaN(+k)) as K[];

View File

@ -1,10 +1,45 @@
import {Angle, Vector3} from "@babylonjs/core";
import round from "round";
import log from "loglevel";
import {IPersistenceManager} from "../diagram/persistenceManager";
export type SnapValue = {
value: number,
label: string
}
export type AppConfigType = {
gridSnap: number,
rotateSnap: number,
createSnap: number
}
export class AppConfig {
public gridSnap = 0;
public rotateSnap = 0;
public gridSnapArray =
[0, 0.1, 0.5, 1];
public rotateSnapArray =
[0, 22.5, 45, 90]
private gridSnap = 0;
private rotateSnap = 0;
private createSnap = 0;
private readonly defaultGridSnapIndex = 1;
private persistenceManager: IPersistenceManager = null;
private gridSnapArray: SnapValue[] =
[{value: 0, label: "Off"},
{value: 0.05, label: "(Default)"},
{value: 0.01, label: "1 cm"},
{value: 0.1, label: "10 cm"},
{value: 0.25, label: "25 cm"},
{value: .5, label: ".5 m"}]
private createSnapArray: SnapValue[] =
[{value: .1, label: "Default (10 cm)"},
{value: 0.2, label: "20 cm"},
{value: 0.5, label: ".5 m"},
{value: 1, label: "1 m"}];
private rotateSnapArray: SnapValue[] =
[{value: 0, label: "Off"},
{value: 22.5, label: "22.5 Degrees"},
{value: 45, label: "45 Degrees"},
{value: 90, label: "90 Degrees"}];
public get currentGridSnap(): SnapValue {
return this.gridSnapArray[this.gridSnap];
}
private static _config: AppConfig;
@ -13,7 +48,116 @@ export class AppConfig {
AppConfig._config = new AppConfig();
}
return AppConfig._config;
}
public get currentRotateSnap(): SnapValue {
return this.rotateSnapArray[this.rotateSnap];
}
public get currentCreateSnap(): SnapValue {
return this.createSnapArray[this.createSnap];
}
public get currentGridSnapIndex(): number {
return this.gridSnap;
}
public set currentGridSnapIndex(val: number) {
this.gridSnap = val;
this.save();
}
public get currentCreateSnapIndex(): number {
return this.createSnap;
}
public set currentCreateSnapIndex(val: number) {
this.createSnap = val;
if (this.currentGridSnapIndex == this.defaultGridSnapIndex) {
this.currentGridSnap.value = this.currentCreateSnap.value / 2;
log.getLogger('AppConfig').debug("Set grid snap to " + this.currentGridSnap.value);
}
this.save();
}
public get currentRotateSnapIndex(): number {
return this.rotateSnap;
}
public set currentRotateSnapIndex(val: number) {
this.rotateSnap = val;
this.save();
}
public get createSnapVal(): Vector3 {
return new Vector3(this.currentCreateSnap.value, this.currentCreateSnap.value, this.currentCreateSnap.value);
}
public setPersistenceManager(persistenceManager: IPersistenceManager) {
this.persistenceManager = persistenceManager;
this.load();
}
public gridSnaps(): SnapValue[] {
return this.gridSnapArray;
}
public createSnaps(): SnapValue[] {
return this.createSnapArray;
}
public rotateSnaps(): SnapValue[] {
return this.rotateSnapArray;
}
public snapGridVal(value: Vector3): Vector3 {
if (this.currentGridSnapIndex == 0) {
return value;
}
const position = value.clone();
position.x = round(position.x, this.currentGridSnap.value);
position.y = round(position.y, this.currentGridSnap.value);
position.z = round(position.z, this.currentGridSnap.value);
return position;
}
public snapRotateVal(value: Vector3): Vector3 {
if (this.currentRotateSnapIndex == 0) {
return value;
}
const rotation = new Vector3();
rotation.x = this.snapAngle(value.x);
rotation.y = this.snapAngle(value.y);
rotation.z = this.snapAngle(value.z);
return rotation;
}
private save() {
this.persistenceManager.setConfig(
{
gridSnap: this.currentGridSnap.value,
rotateSnap: this.currentRotateSnap.value,
createSnap: this.currentCreateSnap.value
});
}
private load() {
const config = this.persistenceManager.getConfig();
if (config) {
this.rotateSnap = this.rotateSnapArray.findIndex((snap) => snap.value == config.rotateSnap);
this.createSnap = this.createSnapArray.findIndex((snap) => snap.value == config.createSnap);
const gridSnap = this.gridSnapArray.findIndex((snap) => snap.value == config.gridSnap);
if (gridSnap == -1) {
this.gridSnap = this.defaultGridSnapIndex;
this.currentGridSnap.value = config.gridSnap;
}
}
}
private snapAngle(val: number): number {
const deg = Angle.FromRadians(val).degrees();
const snappedDegrees = round(deg, this.currentRotateSnap.value);
log.getLogger('AppConfig').debug("deg", val, deg, snappedDegrees, this.currentRotateSnap.value);
return Angle.FromDegrees(snappedDegrees).radians();
}
}