updated so desktop can export diagram created on headset and import to powerpoint.

This commit is contained in:
Michael Mainguy 2023-09-05 19:27:06 -05:00
parent 2cb04a35dc
commit 13a49d63bb
10 changed files with 103 additions and 66 deletions

View File

@ -38,7 +38,7 @@
z-index: 100; z-index: 100;
} }
#hostKey { #download {
position: fixed; position: fixed;
z-index: 11; z-index: 11;
width: 200px; width: 200px;
@ -67,7 +67,7 @@
<div id="questLaunch"><a href="https://www.oculus.com/open_url/?url=https://www.deepdiagram.com/" target="_blank">Launch <div id="questLaunch"><a href="https://www.oculus.com/open_url/?url=https://www.deepdiagram.com/" target="_blank">Launch
On Quest</a> On Quest</a>
</div> </div>
<div id="hostKey">keyLaunch On Quest</div> <div id="download"><a href="#" id="downloadLink">Download Model</a></div>
<script type="module" src="./src/app.ts"></script> <script type="module" src="./src/app.ts"></script>
</body> </body>
</html> </html>

View File

@ -18,6 +18,7 @@ import {Controllers} from "./controllers/controllers";
import workerUrl from "./worker?worker&url"; import workerUrl from "./worker?worker&url";
import {DiagramEventType} from "./diagram/diagramEntity"; import {DiagramEventType} from "./diagram/diagramEntity";
import {PeerjsNetworkConnection} from "./integration/peerjsNetworkConnection"; import {PeerjsNetworkConnection} from "./integration/peerjsNetworkConnection";
import {DiagramExporter} from "./util/diagramExporter";
export class App { export class App {
@ -51,17 +52,24 @@ export class App {
const engine = new Engine(canvas, true); const engine = new Engine(canvas, true);
const scene = new Scene(engine); const scene = new Scene(engine);
const config = new AppConfig(); const config = new AppConfig();
const peerjsNetworkConnection = new PeerjsNetworkConnection();
//const persistenceManager = new IndexdbPersistenceManager("diagram"); //const persistenceManager = new IndexdbPersistenceManager("diagram");
const worker = new Worker(workerUrl, {type: 'module'}); const worker = new Worker(workerUrl, {type: 'module'});
peerjsNetworkConnection.connectionObservable.add((peerId) => {
worker.postMessage({type: 'sync'});
});
const controllers = new Controllers(); const controllers = new Controllers();
const toolbox = new Toolbox(scene, controllers); const toolbox = new Toolbox(scene, controllers);
const diagramManager = new DiagramManager(scene, controllers, toolbox, config); const diagramManager = new DiagramManager(scene, controllers, toolbox, config);
peerjsNetworkConnection.diagramEventObservable.add((evt) => {
this.logger.debug('App', 'peerjs network event', evt);
diagramManager.onDiagramEventObservable.notifyObservers(evt, 1);
});
diagramManager.onDiagramEventObservable.add((evt) => { diagramManager.onDiagramEventObservable.add((evt) => {
this.logger.debug('App', 'diagram event', evt); this.logger.debug('App', 'diagram event', evt);
peerjsNetworkConnection.dataReplicationObservable.notifyObservers(evt);
worker.postMessage({entity: evt}); worker.postMessage({entity: evt});
}, 2); }, 2);
config.onConfigChangedObservable.add((config) => { config.onConfigChangedObservable.add((config) => {
@ -73,6 +81,10 @@ export class App {
if (evt.data.entity) { if (evt.data.entity) {
this.logger.debug('App', 'worker message', evt.data.entity); this.logger.debug('App', 'worker message', evt.data.entity);
peerjsNetworkConnection.dataReplicationObservable.notifyObservers({
type: DiagramEventType.ADD,
entity: evt.data.entity
});
diagramManager.onDiagramEventObservable.notifyObservers({ diagramManager.onDiagramEventObservable.notifyObservers({
type: DiagramEventType.ADD, type: DiagramEventType.ADD,
entity: evt.data.entity entity: evt.data.entity
@ -80,7 +92,6 @@ export class App {
} }
if (evt.data.config) { if (evt.data.config) {
config.onConfigChangedObservable.notifyObservers(evt.data.config, 1); config.onConfigChangedObservable.notifyObservers(evt.data.config, 1);
} }
} }
@ -191,6 +202,14 @@ export class App {
}); });
} }
}); });
const exportLink = document.querySelector('#downloadLink');
if (exportLink) {
exportLink.addEventListener('click', (ev) => {
ev.preventDefault();
const exporter = new DiagramExporter(scene);
exporter.export();
});
}
this.logger.info('keydown event listener added, use Ctrl+Shift+Alt+I to toggle debug layer'); this.logger.info('keydown event listener added, use Ctrl+Shift+Alt+I to toggle debug layer');

View File

@ -70,6 +70,7 @@ export class DiagramConnection {
this.toAnchor.dispose(); this.toAnchor.dispose();
this.toAnchor = toAnchor; this.toAnchor = toAnchor;
this._mesh.metadata.to = this.to; this._mesh.metadata.to = this.to;
this._mesh.metadata.exportable = true;
this._mesh.id = this.id; this._mesh.id = this.id;
this.recalculate(); this.recalculate();
this.setPoints(); this.setPoints();

View File

@ -17,10 +17,7 @@ export function diagramEventHandler(event: DiagramEvent,
sounds: DiaSounds) { sounds: DiaSounds) {
const entity = event.entity; const entity = event.entity;
let mesh; let mesh;
if (entity) { if (event?.entity?.template) {
mesh = scene.getMeshById(entity.id);
}
if (!mesh && event?.entity?.template) {
const toolMesh = scene.getMeshById("tool-" + event.entity.template + "-" + event.entity.color); const toolMesh = scene.getMeshById("tool-" + event.entity.template + "-" + event.entity.color);
if (!toolMesh && event.type != DiagramEventType.CHANGECOLOR) { if (!toolMesh && event.type != DiagramEventType.CHANGECOLOR) {
log.debug('no mesh found for ' + event.entity.template + "-" + event.entity.color, 'adding it'); log.debug('no mesh found for ' + event.entity.template + "-" + event.entity.color, 'adding it');

View File

@ -15,65 +15,63 @@ export function fromDiagramEntity(entity: DiagramEntity, scene: Scene): Abstract
if (!entity.id) { if (!entity.id) {
entity.id = "id" + uuidv4(); entity.id = "id" + uuidv4();
} }
let mesh: AbstractMesh = scene.getMeshById(entity.id); const oldMesh: AbstractMesh = scene.getMeshById(entity.id);
if (mesh) { let newMesh: AbstractMesh;
logger.debug(`mesh ${mesh.id} already exists`); if (oldMesh) {
logger.debug(`mesh ${oldMesh.id} already exists`);
newMesh = oldMesh;
} else { } else {
if (entity.template == "#connection-template") { if (entity.template == "#connection-template") {
const connection: DiagramConnection = new DiagramConnection(entity.from, entity.to, scene); const connection: DiagramConnection = new DiagramConnection(entity.from, entity.to, scene);
logger.debug(`connection.mesh = ${connection.mesh.id}`); logger.debug(`connection.mesh = ${connection.mesh.id}`);
mesh = connection.mesh; newMesh = connection.mesh;
} else { } else {
mesh = scene.getMeshById("tool-" + entity.template + "-" + entity.color); const toolMesh = scene.getMeshById("tool-" + entity.template + "-" + entity.color);
if (mesh) { if (toolMesh && !oldMesh) {
if (mesh.isAnInstance) { newMesh = new InstancedMesh(entity.id, (toolMesh as Mesh));
logger.error(`mesh ${mesh.id} is an instance`); newMesh.metadata = {template: entity.template, exportable: true};
} else { } else {
mesh = new InstancedMesh(entity.id, (mesh as Mesh)); logger.warn('no tool mesh found for ' + entity.template + "-" + entity.color);
} }
} else {
logger.warn('no mesh found for ' + entity.template + "-" + entity.color);
} }
} }
} if (newMesh) {
if (mesh) {
mesh.metadata = {template: entity.template};
if (entity.position) { if (entity.position) {
mesh.position = xyztovec(entity.position); newMesh.position = xyztovec(entity.position);
} }
if (entity.rotation) { if (entity.rotation) {
if (mesh.rotationQuaternion) { if (newMesh.rotationQuaternion) {
mesh.rotationQuaternion = Quaternion.FromEulerAngles(entity.rotation.x, entity.rotation.y, entity.rotation.z); newMesh.rotationQuaternion = Quaternion.FromEulerAngles(entity.rotation.x, entity.rotation.y, entity.rotation.z);
} else { } else {
mesh.rotation = xyztovec(entity.rotation); newMesh.rotation = xyztovec(entity.rotation);
} }
} }
if (entity.parent) { if (entity.parent) {
mesh.parent = scene.getNodeById(entity.parent); newMesh.parent = scene.getNodeById(entity.parent);
} }
if (entity.scale) { if (entity.scale) {
mesh.scaling = xyztovec(entity.scale); newMesh.scaling = xyztovec(entity.scale);
} }
if (!mesh.material) { if (!newMesh.material) {
const material = new StandardMaterial("material-" + entity.id, scene); const material = new StandardMaterial("material-" + entity.id, scene);
material.diffuseColor = Color3.FromHexString(entity.color); material.diffuseColor = Color3.FromHexString(entity.color);
mesh.material = material; newMesh.material = material;
} }
if (entity.text) { if (entity.text) {
mesh.metadata.text = entity.text; newMesh.metadata.text = entity.text;
TextLabel.updateTextNode(mesh, entity.text); TextLabel.updateTextNode(newMesh, entity.text);
} }
if (entity.from) { if (entity.from) {
mesh.metadata.from = entity.from; newMesh.metadata.from = entity.from;
} }
if (entity.to) { if (entity.to) {
mesh.metadata.to = entity.to; newMesh.metadata.to = entity.to;
} }
} else { } else {
logger.error("fromDiagramEntity: mesh is null after it should have been created"); logger.error("fromDiagramEntity: mesh is null after it should have been created");
} }
return mesh; return newMesh;
} }

View File

@ -51,7 +51,7 @@ export class TextLabel {
const plane = MeshBuilder.CreatePlane("text" + text, {width: planeWidth, height: height}, mesh.getScene()); const plane = MeshBuilder.CreatePlane("text" + text, {width: planeWidth, height: height}, mesh.getScene());
plane.material = mat; plane.material = mat;
plane.billboardMode = Mesh.BILLBOARDMODE_ALL; plane.billboardMode = Mesh.BILLBOARDMODE_ALL;
plane.metadata = {exportable: true};
const yOffset = mesh.getBoundingInfo().boundingSphere.radius; const yOffset = mesh.getBoundingInfo().boundingSphere.radius;
plane.parent = mesh; plane.parent = mesh;

View File

@ -1,15 +1,17 @@
import P2PDataChannel from 'p2p-data-channel'; import P2PDataChannel from 'p2p-data-channel';
import log from "loglevel"; import log from "loglevel";
import {Observable} from "@babylonjs/core"; import {Observable} from "@babylonjs/core";
import {DiagramEvent, DiagramEventMask} from "../diagram/diagramEntity"; import {DiagramEvent} from "../diagram/diagramEntity";
export class PeerjsNetworkConnection { export class PeerjsNetworkConnection {
private logger: log.Logger = log.getLogger('PeerjsNetworkConnection'); private logger: log.Logger = log.getLogger('PeerjsNetworkConnection');
private dataChannel: P2PDataChannel<any>; private dataChannel: P2PDataChannel<any>;
public readonly connectionObservable: Observable<any> = new Observable<any>;
public readonly dataReplicationObservable: Observable<any> = new Observable<any>;
public readonly diagramEventObservable: Observable<DiagramEvent> = new Observable<DiagramEvent>;
private readonly onDiagramEventObservable: Observable<DiagramEvent>; constructor() {
constructor(onDiagramEventObservable: Observable<DiagramEvent>) {
const config = { const config = {
debug: false, debug: false,
dataChannel: 'deepSharedDiagram', dataChannel: 'deepSharedDiagram',
@ -22,11 +24,10 @@ export class PeerjsNetworkConnection {
// @ts-ignore // @ts-ignore
const passphrase = window.niceware.generatePassphrase(6).join('-'); const passphrase = window.niceware.generatePassphrase(6).join('-');
this.logger.debug('Local Passphrase: ', passphrase); this.logger.debug('Local Passphrase: ', passphrase);
this.onDiagramEventObservable = onDiagramEventObservable;
this.dataChannel = new P2PDataChannel(passphrase, config); this.dataChannel = new P2PDataChannel(passphrase, config);
this.dataChannel.onConnected((peerId) => { this.dataChannel.onConnected((peerId) => {
this.logger.debug('Connected to ', peerId); this.logger.debug('Connected to ', peerId);
this.connectionObservable.notifyObservers(peerId);
}); });
this.dataChannel.onMessage((message) => { this.dataChannel.onMessage((message) => {
this.logger.debug(message); this.logger.debug(message);
@ -38,8 +39,7 @@ export class PeerjsNetworkConnection {
} }
if (message.payload.diagramEvent) { if (message.payload.diagramEvent) {
this.logger.debug('Received diagram event from ', message.sender, message.payload.diagramEvent); this.logger.debug('Received diagram event from ', message.sender, message.payload.diagramEvent);
const event = message.payload.diagramEvent; this.diagramEventObservable.notifyObservers(message.payload.diagramEvent);
this.onDiagramEventObservable.notifyObservers(event, DiagramEventMask.REMOTE);
} }
} }
}); });
@ -52,6 +52,10 @@ export class PeerjsNetworkConnection {
const linkEl = document.querySelector('#questLaunch a'); const linkEl = document.querySelector('#questLaunch a');
linkEl.setAttribute('href', link + passphrase); linkEl.setAttribute('href', link + passphrase);
} }
this.dataReplicationObservable.add((evt) => {
this.logger.debug(evt);
this.dataChannel.broadcast({diagramEvent: evt});
});
} }
public connectToRemote(host: string) { public connectToRemote(host: string) {

View File

@ -25,6 +25,7 @@ import {toDiagramEntity} from "../diagram/functions/toDiagramEntity";
import {AbstractMenu} from "./abstractMenu"; import {AbstractMenu} from "./abstractMenu";
import {Controllers} from "../controllers/controllers"; import {Controllers} from "../controllers/controllers";
import {setMenuPosition} from "../util/functions/setMenuPosition"; import {setMenuPosition} from "../util/functions/setMenuPosition";
import {DiagramExporter} from "../util/diagramExporter";
export class EditMenu extends AbstractMenu { export class EditMenu extends AbstractMenu {
private state: EditMenuState = EditMenuState.NONE; private state: EditMenuState = EditMenuState.NONE;
@ -310,24 +311,7 @@ export class EditMenu extends AbstractMenu {
this.showNewRelic(); this.showNewRelic();
break; break;
case "export": case "export":
import("@babylonjs/serializers").then((serializers) => { this.download();
serializers.GLTF2Export.GLBAsync(this.scene, 'diagram.glb', {
shouldExportNode: function (node) {
if (node?.metadata?.template) {
return true;
} else {
return false;
}
}
}).then((gltf) => {
gltf.downloadFiles();
});
});
break; break;
default: default:
this.logger.error("Unknown button"); this.logger.error("Unknown button");
@ -337,4 +321,9 @@ export class EditMenu extends AbstractMenu {
this.isVisible = false; this.isVisible = false;
} }
private download() {
const exporter = new DiagramExporter(this.scene);
exporter.export();
}
} }

View File

@ -0,0 +1,27 @@
import {Scene} from "@babylonjs/core";
export class DiagramExporter {
private scene: Scene;
constructor(scene: Scene) {
this.scene = scene;
}
public export() {
import("@babylonjs/serializers").then((serializers) => {
serializers.GLTF2Export.GLBAsync(this.scene, 'diagram.glb', {
shouldExportNode: function (node) {
if (node?.metadata?.exportable) {
return true;
} else {
return false;
}
}
}).then((gltf) => {
gltf.downloadFiles();
});
});
}
}

View File

@ -8,7 +8,9 @@ const ctx: Worker = self as any;
ctx.onmessage = (event) => { ctx.onmessage = (event) => {
console.log(event); console.log(event);
if (event.data.type == 'sync') {
persistenceManager.sync();
}
if (event.data.type == 'init') { if (event.data.type == 'init') {
persistenceManager.updateObserver.add((event) => { persistenceManager.updateObserver.add((event) => {
console.log(event); console.log(event);