diff --git a/packages/objectloader/src/index.js b/packages/objectloader/src/index.js index 761b0a9d85..ef4ec423d2 100644 --- a/packages/objectloader/src/index.js +++ b/packages/objectloader/src/index.js @@ -119,6 +119,7 @@ export default class ObjectLoader { dispose() { this.buffer = [] + this.promises = [] Object.values(this.intervals).forEach((i) => clearInterval(i.interval)) } diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index b8fbf646e8..bff383aa09 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -18,6 +18,8 @@ import { DiffExtension, FilteringExtension } from '@speckle/viewer' +import { SectionTool } from '@speckle/viewer' +import { SectionOutlines } from '@speckle/viewer' const createViewer = async (containerName: string, stream: string) => { const container = document.querySelector(containerName) @@ -43,8 +45,8 @@ const createViewer = async (containerName: string, stream: string) => { const cameraController = viewer.createExtension(CameraController) const selection = viewer.createExtension(SelectionExtension) - // const sections = viewer.createExtension(SectionTool) - // const sectionOutlines = viewer.createExtension(SectionOutlines) + const sections = viewer.createExtension(SectionTool) + const sectionOutlines = viewer.createExtension(SectionOutlines) const measurements = viewer.createExtension(MeasurementsExtension) const filtering = viewer.createExtension(FilteringExtension) const explode = viewer.createExtension(ExplodeExtension) @@ -53,8 +55,8 @@ const createViewer = async (containerName: string, stream: string) => { // const rotateCamera = viewer.createExtension(RotateCamera) cameraController // use it selection // use it - // sections // use it - // sectionOutlines // use it + sections // use it + sectionOutlines // use it measurements // use it filtering // use it explode // use it @@ -119,7 +121,6 @@ const getStream = () => { // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' // Revit sample house (good for bim-like stuff with many display meshes) // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' - // 'https://latest.speckle.dev/streams/c1faab5c62/commits/6c6e43e5f3' // 'https://latest.speckle.dev/streams/58b5648c4d/commits/60371ecb2d' // 'Super' heavy revit shit // 'https://speckle.xyz/streams/e6f9156405/commits/0694d53bb5' @@ -347,12 +348,14 @@ const getStream = () => { // 'https://latest.speckle.dev/streams/92b620fb17/objects/7118603b197c00944f53be650ce721ec' // Blender Mega Test Stream - 'https://latest.speckle.dev/streams/c1faab5c62/commits/2ecb757577' + // 'https://latest.speckle.dev/streams/c1faab5c62/commits/2ecb757577' // 'https://latest.speckle.dev/streams/c1faab5c62/commits/3deaea94af' // Text and Dimensions // 'https://latest.speckle.dev/streams/3f895e614f/commits/fbc78286c9' // 'https://latest.speckle.dev/streams/55cc1cbf0a/commits/aa72674507' // 'https://latest.speckle.dev/streams/55cc1cbf0a/commits/a7f74b6524' + // 'https://latest.speckle.dev/streams/85e05b8c72/commits/53f4328211' + 'https://latest.speckle.dev/streams/aea12cab71/commits/787ade768e' ) } diff --git a/packages/viewer/src/IViewer.ts b/packages/viewer/src/IViewer.ts index b9c33e2c91..c25422a6d9 100644 --- a/packages/viewer/src/IViewer.ts +++ b/packages/viewer/src/IViewer.ts @@ -120,8 +120,9 @@ export enum ObjectLayers { } export enum UpdateFlags { - RENDER = 1, - SHADOWS = 2 + RENDER = 0b1, + SHADOWS = 0b10, + CLIPPING_PLANES = 0b100 } export interface IViewer { diff --git a/packages/viewer/src/modules/Intersections.ts b/packages/viewer/src/modules/Intersections.ts index 409b1039b8..1d9d667536 100644 --- a/packages/viewer/src/modules/Intersections.ts +++ b/packages/viewer/src/modules/Intersections.ts @@ -148,7 +148,12 @@ export class Intersections { ) ) results = results.filter((result) => { - return this.boundsBuffer.containsPoint(result.point) + return ( + this.boundsBuffer.containsPoint(result.point) || + (result.pointOnLine + ? this.boundsBuffer.containsPoint(result.pointOnLine) + : false) + ) }) } diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index a488aae0bd..110fe4a63e 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -157,7 +157,7 @@ export default class SpeckleRenderer { } public get clippingVolume(): Box3 { - return !this._clippingVolume.isEmpty() + return !this._clippingVolume.isEmpty() && this._renderer.localClippingEnabled ? new Box3().copy(this._clippingVolume) : this.sceneBox } @@ -609,6 +609,7 @@ export default class SpeckleRenderer { /** We'll just update the shadowcatcher after all batches are loaded */ this.updateShadowCatcher() + this.updateClippingPlanes() delete this.cancel[subtreeId] } @@ -801,9 +802,9 @@ export default class SpeckleRenderer { return this.batcher.batches[id] } - protected updateClippingPlanes(planes?: Plane[]) { + public updateClippingPlanes() { if (!this.allObjects) return - if (!planes) planes = this._clippingPlanes + const planes = this._clippingPlanes this.allObjects.traverse((object) => { const material = (object as unknown as { material }).material diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index b06ca30175..58f965f395 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -176,6 +176,9 @@ export class Viewer extends EventEmitter implements IViewer { if (flags & UpdateFlags.SHADOWS) { this.speckleRenderer.shadowMapNeedsUpdate = true } + if (flags & UpdateFlags.CLIPPING_PLANES) { + this.speckleRenderer.updateClippingPlanes() + } } private frame() { @@ -298,7 +301,6 @@ export class Viewer extends EventEmitter implements IViewer { this.loaders[loader.resource] = loader const treeBuilt = await loader.load() - if (treeBuilt) { const t0 = performance.now() for await (const step of this.speckleRenderer.addRenderTree(loader.resource)) { @@ -318,7 +320,7 @@ export class Viewer extends EventEmitter implements IViewer { this.emit(ViewerEvent.LoadComplete, loader.resource) } - this.loaders[loader.resource].dispose() + if (this.loaders[loader.resource]) this.loaders[loader.resource].dispose() delete this.loaders[loader.resource] if (--this.inProgressOperations === 0) (this as EventEmitter).emit(ViewerEvent.Busy, false) @@ -341,6 +343,10 @@ export class Viewer extends EventEmitter implements IViewer { if (++this.inProgressOperations === 1) (this as EventEmitter).emit(ViewerEvent.Busy, true) if (this.tree.findSubtree(resource)) { + if (this.loaders[resource]) { + await this.cancelLoad(resource, true) + return + } delete this.loaders[resource] this.speckleRenderer.removeRenderTree(resource) this.tree.getRenderTree(resource).purge() @@ -361,6 +367,7 @@ export class Viewer extends EventEmitter implements IViewer { if (++this.inProgressOperations === 1) (this as EventEmitter).emit(ViewerEvent.Busy, true) for (const key of Object.keys(this.loaders)) { + if (this.loaders[key]) await this.cancelLoad(key, false) delete this.loaders[key] } this.tree.root.children.forEach((node) => { diff --git a/packages/viewer/src/modules/batching/Batcher.ts b/packages/viewer/src/modules/batching/Batcher.ts index 42b6cb01be..205bbeb32e 100644 --- a/packages/viewer/src/modules/batching/Batcher.ts +++ b/packages/viewer/src/modules/batching/Batcher.ts @@ -59,6 +59,9 @@ export default class Batcher { } let instancedNodes = worldTree.findId(g) + if (!instancedNodes) { + continue + } instancedNodes = instancedNodes.filter((node: TreeNode) => { return ( node.model.renderView && diff --git a/packages/viewer/src/modules/extensions/FilteringExtension.ts b/packages/viewer/src/modules/extensions/FilteringExtension.ts index acb9f7f1d0..c5a64ecb6e 100644 --- a/packages/viewer/src/modules/extensions/FilteringExtension.ts +++ b/packages/viewer/src/modules/extensions/FilteringExtension.ts @@ -338,9 +338,11 @@ export class FilteringExtension extends Extension { } public removeColorFilter(): FilteringState { - this.ColorStringFilterState = null - this.ColorNumericFilterState = null - return this.setFilters() + if (this.ColorNumericFilterState || this.ColorStringFilterState) { + this.ColorStringFilterState = null + this.ColorNumericFilterState = null + return this.setFilters() + } } public setUserObjectColors(groups: { objectIds: string[]; color: string }[]) { @@ -512,7 +514,9 @@ export class FilteringExtension extends Extension { } } - this.Renderer.viewer.requestRender(UpdateFlags.RENDER | UpdateFlags.SHADOWS) + this.Renderer.viewer.requestRender( + UpdateFlags.RENDER | UpdateFlags.SHADOWS | UpdateFlags.CLIPPING_PLANES + ) this.emit(ViewerEvent.FilteringStateSet, this.CurrentFilteringState) return this.CurrentFilteringState } diff --git a/packages/viewer/src/modules/extensions/SectionTool.ts b/packages/viewer/src/modules/extensions/SectionTool.ts index 803b68e34d..e55fb28a63 100644 --- a/packages/viewer/src/modules/extensions/SectionTool.ts +++ b/packages/viewer/src/modules/extensions/SectionTool.ts @@ -423,7 +423,7 @@ export class SectionTool extends Extension implements ISectionProvider { return this.boxGeometry.boundingBox } - public setBox(targetBox, offset = 0.05) { + public setBox(targetBox, offset = 0) { let box if (targetBox) box = targetBox diff --git a/packages/viewer/src/modules/extensions/SelectionExtension.ts b/packages/viewer/src/modules/extensions/SelectionExtension.ts index 1e4321865e..7e44307faa 100644 --- a/packages/viewer/src/modules/extensions/SelectionExtension.ts +++ b/packages/viewer/src/modules/extensions/SelectionExtension.ts @@ -5,7 +5,13 @@ import { NodeRenderView } from '../tree/NodeRenderView' import { Material } from 'three' import { InputEvent } from '../input/Input' import { MathUtils } from 'three' -import { IViewer, ObjectLayers, SelectionEvent, ViewerEvent } from '../../IViewer' +import { + IViewer, + ObjectLayers, + SelectionEvent, + UpdateFlags, + ViewerEvent +} from '../../IViewer' import Materials, { DisplayStyle, MaterialOptions, @@ -240,7 +246,7 @@ export class SelectionExtension extends Extension { this.viewer .getRenderer() .setMaterial(transparentRvs, this.transparentSelectionMaterialData) - this.viewer.requestRender() + this.viewer.requestRender(UpdateFlags.RENDER | UpdateFlags.CLIPPING_PLANES) } protected removeSelection(rvs?: Array) { diff --git a/packages/viewer/src/modules/loaders/Loader.ts b/packages/viewer/src/modules/loaders/Loader.ts index f9611b4bf1..469fd70443 100644 --- a/packages/viewer/src/modules/loaders/Loader.ts +++ b/packages/viewer/src/modules/loaders/Loader.ts @@ -12,6 +12,7 @@ export abstract class Loader extends EventEmitter { protected _resourceData: string | ArrayBuffer public abstract get resource(): string + public abstract get finished(): boolean protected constructor(resource: string, resourceData: string | ArrayBuffer) { super() diff --git a/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts b/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts index 0ba78b486e..e67c5207e7 100644 --- a/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts +++ b/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts @@ -10,11 +10,16 @@ export class ObjLoader extends Loader { private baseLoader: OBJLoader private converter: ObjConverter private tree: WorldTree + private isFinished: boolean public get resource(): string { return this._resource } + public get finished(): boolean { + return this.isFinished + } + public constructor(targetTree: WorldTree, resource: string, resourceData?: string) { super(resource, resourceData) this.tree = targetTree @@ -65,7 +70,7 @@ export class ObjLoader extends Loader { .getRenderTree(this._resource) .buildRenderTree(new ObjGeometryConverter()) Logger.log('Tree build time -> ', performance.now() - t0) - + this.isFinished = true resolve(res) }) pload.catch(() => { @@ -76,6 +81,7 @@ export class ObjLoader extends Loader { } public cancel() { + this.isFinished = false throw new Error('Method not implemented.') } public dispose() { diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts index 277c1fb909..d6a90ca496 100644 --- a/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts +++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts @@ -12,11 +12,16 @@ export class SpeckleLoader extends Loader { private tree: WorldTree private priority: number = 1 private isCancelled = false + private isFinished = false public get resource(): string { return this._resource } + public get finished(): boolean { + return this.isFinished + } + constructor( targetTree: WorldTree, resource: string, @@ -82,7 +87,7 @@ export class SpeckleLoader extends Loader { for await (const obj of this.loader.getObjectIterator()) { if (this.isCancelled) { this.emit(LoaderEvent.LoadCancelled, this._resource) - return + return Promise.resolve(false) } if (first) { firstObjectPromise = this.converter.traverse(this._resource, obj, async () => { @@ -118,18 +123,27 @@ export class SpeckleLoader extends Loader { message: `No displayable objects found in object ${this._resource}.` }) } + if (this.isCancelled) { + return Promise.resolve(false) + } + const t0 = performance.now() const geometryConverter = new SpeckleGeometryConverter() - const p = this.tree.getRenderTree(this._resource).buildRenderTree(geometryConverter) + + const renderTree = this.tree.getRenderTree(this._resource) + if (!renderTree) return Promise.resolve(false) + const p = renderTree.buildRenderTree(geometryConverter) p.then(() => { Logger.log('ASYNC Tree build time -> ', performance.now() - t0) + this.isFinished = true }) return p } cancel() { this.isCancelled = true + this.isFinished = false } dispose() { diff --git a/packages/viewer/src/modules/tree/WorldTree.ts b/packages/viewer/src/modules/tree/WorldTree.ts index 5b02bd85bd..7c0bd2a990 100644 --- a/packages/viewer/src/modules/tree/WorldTree.ts +++ b/packages/viewer/src/modules/tree/WorldTree.ts @@ -45,6 +45,9 @@ export class WorldTree { } const renderTreeRoot = subtreeId ? this.findSubtree(subtreeId) : this.root + if (!renderTreeRoot) { + return null + } const subtreeRootId = renderTreeRoot.model.id if (!this.renderTreeInstances[subtreeRootId]) { this.renderTreeInstances[subtreeRootId] = new RenderTree(this, renderTreeRoot) @@ -99,7 +102,7 @@ export class WorldTree { return } node.model.subtreeId = parent.model.subtreeId - if (this.nodeMaps[parent.model.subtreeId].addNode(node)) parent.addChild(node) + if (this.nodeMaps[parent.model.subtreeId]?.addNode(node)) parent.addChild(node) } public removeNode(node: TreeNode) {