From 66cd68077d2586a9028d2d8fbd0a8bdfb6b085d4 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Fri, 12 Aug 2022 16:34:04 +0300 Subject: [PATCH] #Integration: Implemented getViews and setViews at viewer API level. They still use the old 'way of working' but changing that will be trivial, since views are now part of the world tree liek everything else. Implemented canonical views, implemented screenshot. All of these are controllable from the sandbox for testing --- packages/viewer-sandbox/src/Sandbox.ts | 56 ++++++++++++ packages/viewer-sandbox/src/main.ts | 4 +- packages/viewer/src/IViewer.ts | 6 ++ .../viewer/src/modules/SpeckleRenderer.ts | 87 ++++++++++++++++++- packages/viewer/src/modules/Viewer.ts | 47 ++++++++-- 5 files changed, 192 insertions(+), 8 deletions(-) diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index b9047a096..6af5887a9 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -10,6 +10,8 @@ export default class Sandbox { private tabs private filterControls private steamsFolder + private viewsControls = [] + private viewsFolder private streams: { [url: string]: Array } = {} public static urlParams = { @@ -47,6 +49,17 @@ export default class Sandbox { viewer.on('load-complete', (url: string) => { this.addStreamControls(url) + this.addViewControls() + }) + viewer.on('unload-complete', (url: string) => { + this.removeViewControls() + this.addViewControls() + url + }) + viewer.on('unload-all-complete', (url: string) => { + this.removeViewControls() + this.addViewControls() + url }) } @@ -89,6 +102,27 @@ export default class Sandbox { delete this.streams[url] } + private addViewControls() { + const views = this.viewer.getViews() + this.viewsFolder = this.tabs.pages[0].addFolder({ + title: 'Views', + expanded: true + }) + for (let k = 0; k < views.length; k++) { + this.viewsFolder + .addButton({ + title: views[k].name + }) + .on('click', () => { + this.viewer.setView(views[k].id) + }) + } + } + + private removeViewControls() { + this.viewsFolder.dispose() + } + public makeGenericUI() { this.tabs.pages[0].addInput(Sandbox.urlParams, 'url', { title: 'url' @@ -148,6 +182,28 @@ export default class Sandbox { showBatches.on('click', () => { this.viewer.speckleRenderer.debugShowBatches() }) + + const screenshot = this.tabs.pages[0].addButton({ + title: 'Screenshot' + }) + screenshot.on('click', async () => { + console.warn(await this.viewer.screenshot()) + }) + + const canonicalViewsFolder = this.tabs.pages[0].addFolder({ + title: 'Canonical Views', + expanded: true + }) + const sides = ['front', 'back', 'top', 'bottom', 'right', 'left', '3d'] + for (let k = 0; k < sides.length; k++) { + canonicalViewsFolder + .addButton({ + title: sides[k] + }) + .on('click', () => { + this.viewer.rotateTo(sides[k]) + }) + } } makeSceneUI() { diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 7069c5751..229dd45af 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -40,4 +40,6 @@ sandbox.makeGenericUI() sandbox.makeSceneUI() sandbox.makeFilteringUI() // Load demo object -sandbox.loadUrl('https://latest.speckle.dev/streams/ca0378725b/commits/e5a66562cc') +sandbox.loadUrl( + 'https://latest.speckle.dev/streams/92b620fb17/commits/0ffebbd2f6?c=%5B-30.45977,17.60411,23.71672,-2.20374,1.01037,3.58707,0,1%5D' +) diff --git a/packages/viewer/src/IViewer.ts b/packages/viewer/src/IViewer.ts index bf101b476..f24d50c94 100644 --- a/packages/viewer/src/IViewer.ts +++ b/packages/viewer/src/IViewer.ts @@ -47,12 +47,18 @@ export interface IViewer { sectionBoxOn(): void zoomExtents(fit?: number, transition?: boolean): void toggleCameraProjection(): void + getViews() + setView(id: string, transition: boolean) + // This shouldn't be part of the API, it should be handled through `setView` + rotateTo(side: string, transition: boolean) loadObject(url: string, token?: string, enableCaching?: boolean): Promise cancelLoad(url: string, unload?: boolean): Promise unloadObject(url: string): Promise unloadAll(): Promise + screenshot(): Promise + applyFilter(filter: unknown): Promise getObjectsProperties(includeAll?: boolean): unknown diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index ded070c6c..75727bec3 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -403,7 +403,7 @@ export default class SpeckleRenderer { } /** Taken from InteractionsHandler. Will revisit in the future */ - zoomToBox(box, fit = 1.2, transition = true) { + public zoomToBox(box, fit = 1.2, transition = true) { if (box.max.x === Infinity || box.max.x === -Infinity) { box = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1)) } @@ -452,6 +452,91 @@ export default class SpeckleRenderer { } } + public setView(origin: Vector3, target: Vector3, transition = true) { + this.viewer.cameraHandler.activeCam.controls.setLookAt( + origin.x, + origin.y, + origin.z, + target.x, + target.y, + target.z, + transition + ) + } + + /** + * Rotates camera to some canonical views + * @param {string} side Can be any of front, back, up (top), down (bottom), right, left. + * @param {Number} fit [description] + * @param {Boolean} transition [description] + * @return {[type]} [description] + */ + public rotateTo(side: string, transition = true) { + const DEG90 = Math.PI * 0.5 + const DEG180 = Math.PI + + switch (side) { + case 'front': + this.viewer.cameraHandler.controls.rotateTo(0, DEG90, transition) + if (this.viewer.cameraHandler.activeCam.name === 'ortho') + this.viewer.cameraHandler.disableRotations() + break + + case 'back': + this.viewer.cameraHandler.controls.rotateTo(DEG180, DEG90, transition) + if (this.viewer.cameraHandler.activeCam.name === 'ortho') + this.viewer.cameraHandler.disableRotations() + break + + case 'up': + case 'top': + this.viewer.cameraHandler.controls.rotateTo(0, 0, transition) + if (this.viewer.cameraHandler.activeCam.name === 'ortho') + this.viewer.cameraHandler.disableRotations() + break + + case 'down': + case 'bottom': + this.viewer.cameraHandler.controls.rotateTo(0, DEG180, transition) + if (this.viewer.cameraHandler.activeCam.name === 'ortho') + this.viewer.cameraHandler.disableRotations() + break + + case 'right': + this.viewer.cameraHandler.controls.rotateTo(DEG90, DEG90, transition) + if (this.viewer.cameraHandler.activeCam.name === 'ortho') + this.viewer.cameraHandler.disableRotations() + break + + case 'left': + this.viewer.cameraHandler.controls.rotateTo(-DEG90, DEG90, transition) + if (this.viewer.cameraHandler.activeCam.name === 'ortho') + this.viewer.cameraHandler.disableRotations() + break + + case '3d': + case '3D': + default: { + let box + if (this.allObjects.children.length === 0) + box = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1)) + else box = new Box3().setFromObject(this.allObjects) + if (box.max.x === Infinity || box.max.x === -Infinity) { + box = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1)) + } + this.viewer.cameraHandler.controls.setPosition( + box.max.x, + box.max.y, + box.max.z, + transition + ) + this.zoomExtents() + this.viewer.cameraHandler.enableRotations() + break + } + } + } + /** DEBUG */ public onObjectClickDebug(e) { const result: Intersection = this.intersections.intersect( diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index efa954732..38604919a 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -102,9 +102,6 @@ export class Viewer extends EventEmitter implements IViewer { WorldTree.getRenderTree(url).buildRenderTree() this.speckleRenderer.addRenderTree(url) this.zoomExtents() - const views = this.__getViews() - console.warn(`Found ${views.length} VIEWS:`) - console.warn(views) console.warn('Built stuff') }) } @@ -173,9 +170,45 @@ export class Viewer extends EventEmitter implements IViewer { this.cameraHandler.toggleCameras() } - public __getViews() { - return WorldTree.getInstance().findAll((node: TreeNode) => { - return node.model.renderView?.speckleType === SpeckleType.View3D + public getViews() { + return WorldTree.getInstance() + .findAll((node: TreeNode) => { + return node.model.renderView?.speckleType === SpeckleType.View3D + }) + .map((v) => { + return { + name: v.model.raw.applicationId, + id: v.model.id, + view: v.model.raw + } + }) + } + + public setView(id: string, transition: boolean): void { + const view3DNode = WorldTree.getInstance().findId(id) + this.speckleRenderer.setView( + view3DNode.model.raw.origin, + view3DNode.model.raw.target, + transition + ) + } + + public rotateTo(side: string, transition = true) { + this.speckleRenderer.rotateTo(side) + transition + } + + public screenshot(): Promise { + return new Promise((resolve) => { + const sectionBoxVisible = this.sectionBox.display.visible + if (sectionBoxVisible) { + this.sectionBox.displayOff() + } + const screenshot = this.speckleRenderer.renderer.domElement.toDataURL('image/png') + if (sectionBoxVisible) { + this.sectionBox.displayOn() + } + resolve(screenshot) }) } @@ -210,6 +243,7 @@ export class Viewer extends EventEmitter implements IViewer { if (--this.inProgressOperations === 0) { ;(this as EventEmitter).emit('busy', false) console.warn(`Removed subtree ${url}`) + ;(this as EventEmitter).emit('unload-complete', url) } } } @@ -231,6 +265,7 @@ export class Viewer extends EventEmitter implements IViewer { if (--this.inProgressOperations === 0) { ;(this as EventEmitter).emit('busy', false) console.warn(`Removed all subtrees`) + ;(this as EventEmitter).emit('unload-all-complete') } } }