diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 079d86b68..98f4243d5 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -105,7 +105,7 @@ const getStream = () => { // prettier-ignore // '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://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.dev/streams/c1faab5c62/commits/ab1a1ab2b6' // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.dev/streams/58b5648c4d/commits/60371ecb2d' @@ -393,6 +393,10 @@ const getStream = () => { // 'https://app.speckle.systems/projects/47bbaf594f/models/de52414725f8937b1f0e2f550ef9ca52' // qGIS sRGB vertex colors // 'https://latest.speckle.systems/projects/5a6609a4b9/models/10f4931e8c' + // DUI3 blocks + // 'https://latest.speckle.systems/projects/126cd4b7bb/models/c6f3f309a2' + 'https://latest.speckle.systems/projects/126cd4b7bb/models/6b62c61a22' + // 'https://latest.speckle.systems/projects/126cd4b7bb/models/4dc5265453' ) } diff --git a/packages/viewer/src/IViewer.ts b/packages/viewer/src/IViewer.ts index e6d7cacb0..8b77845d6 100644 --- a/packages/viewer/src/IViewer.ts +++ b/packages/viewer/src/IViewer.ts @@ -25,6 +25,7 @@ export type SpeckleObject = { name?: string referencedId?: string units?: string + applicationId?: string } export interface ViewerParams { diff --git a/packages/viewer/src/modules/loaders/GeometryConverter.ts b/packages/viewer/src/modules/loaders/GeometryConverter.ts index 424aed286..906626d29 100644 --- a/packages/viewer/src/modules/loaders/GeometryConverter.ts +++ b/packages/viewer/src/modules/loaders/GeometryConverter.ts @@ -19,6 +19,7 @@ export enum SpeckleType { RevitInstance = 'RevitInstance', Text = 'Text', Transform = 'Transform', + InstanceProxy = 'InstanceProxy', Unknown = 'Unknown' } diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts index bcf4c3e65..334e2cc32 100644 --- a/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts +++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { MathUtils } from 'three' +import { MathUtils, Matrix4 } from 'three' import { type TreeNode, WorldTree } from '../../tree/WorldTree.js' import { NodeMap } from '../../tree/NodeMap.js' -import type { SpeckleObject } from '../../../index.js' +import { type SpeckleObject } from '../../../index.js' import type ObjectLoader from '@speckle/objectloader' import Logger from '../../utils/Logger.js' @@ -22,6 +22,9 @@ export default class SpeckleConverter { private spoofIDs = false private tree: WorldTree private typeLookupTable: { [type: string]: string } = {} + private instanceDefinitionLookupTable: { [id: string]: TreeNode } = {} + private instancedObjectsLookupTable: { [id: string]: SpeckleObject } = {} + private instanceProxies: { [id: string]: TreeNode } = {} private instanceCounter = 0 private readonly NodeConverterMapping: { @@ -44,6 +47,8 @@ export default class SpeckleConverter { RevitInstance: this.RevitInstanceToNode.bind(this), Text: this.TextToNode.bind(this), Dimension: this.DimensionToNode.bind(this), + InstanceDefinitionProxy: this.InstanceDefinitionProxyToNode.bind(this), + InstanceProxy: this.InstanceProxyToNode.bind(this), Parameter: null } @@ -521,6 +526,130 @@ export default class SpeckleConverter { } } + private async InstanceDefinitionProxyToNode(obj: SpeckleObject, node: TreeNode) { + if (!obj.applicationId) { + Logger.warn(`Instance Definition Proxy ${obj.id} has no applicationId`) + return + } + this.instanceDefinitionLookupTable[obj.applicationId] = node + } + + private async InstanceProxyToNode(obj: SpeckleObject, node: TreeNode) { + if (!obj.applicationId) { + Logger.warn(`Instance proxy ${obj.id} has no application id`) + return + } + this.instanceProxies[obj.applicationId] = node + return + } + + private getInstanceProxyDefinitionId(obj: SpeckleObject): string { + return (obj.DefinitionId || obj.definitionId) as string + } + + private getInstanceProxyTransform(obj: SpeckleObject): Array { + if (!(obj.transform || obj.Transform)) { + return new Matrix4().toArray() + } + return (obj.transform || obj.Transform) as Array + } + + private getInstanceProxyDefinitionObjects(obj: SpeckleObject): Array { + return (obj.Objects || obj.objects) as Array + } + + private createTransformNode(obj: SpeckleObject) { + const transformNodeId = MathUtils.generateUUID() + const transformData = this.getEmptyTransformData(transformNodeId) + transformData.units = obj.units as string + transformData.matrix = this.getInstanceProxyTransform(obj) + return this.tree.parse({ + id: transformNodeId, + raw: transformData, + atomic: false, + children: [] + }) + } + + private async ConvertInstanceProxyToNode(obj: SpeckleObject, node: TreeNode) { + const definitionId = this.getInstanceProxyDefinitionId(obj) + if (!definitionId) { + Logger.warn(`Instance Proxy ${obj.id} has no definitionId`) + return + } + const definition = this.instanceDefinitionLookupTable[definitionId] + const transformNode = this.createTransformNode(obj) + + this.tree.addNode(transformNode, node) + const objectApplicationIds = this.getInstanceProxyDefinitionObjects( + definition.model.raw + ) + for (const objectApplicationId of objectApplicationIds) { + const speckleData = this.instancedObjectsLookupTable[objectApplicationId] + const instancedNode = this.tree.parse({ + id: this.getCompoundId(speckleData.id, this.instanceCounter++), + raw: speckleData, + atomic: false, + children: [], + instanced: true + }) + this.tree.addNode(instancedNode, transformNode) + await this.convertToNode(speckleData, instancedNode) + } + } + + public async convertInstances() { + /** uh, oh */ + this.NodeConverterMapping.InstanceProxy = this.ConvertInstanceProxyToNode.bind(this) + + /** Find the nodes that need to be 'consumed' */ + const consumeApplicationIds: { [id: string]: TreeNode | null } = {} + let consumeApplicationIdsCount = 0 + for (const k in this.instanceDefinitionLookupTable) { + const definition = this.instanceDefinitionLookupTable[k] + const objects = this.getInstanceProxyDefinitionObjects(definition.model.raw) + for (let i = 0; i < objects.length; i++) { + consumeApplicationIds[objects[i].toString()] = null + consumeApplicationIdsCount++ + } + } + /** Do a short async walk */ + await this.tree.walkAsync((node: TreeNode) => { + if (!node.model.raw.applicationId) return true + const applicationId = node.model.raw.applicationId.toString() + if (consumeApplicationIds[applicationId] !== undefined) { + consumeApplicationIds[applicationId] = node + consumeApplicationIdsCount-- + } + /** Break out when all applicationIds are accounted for*/ + if (consumeApplicationIdsCount === 0) return false + return true + }) + + /** Consume them */ + for (const k in consumeApplicationIds) { + const objectNode = consumeApplicationIds[k] + if (!objectNode) { + Logger.error(`Consumable applicationId ${k} could not be found`) + continue + } + + /** Store the speckle object data */ + this.instancedObjectsLookupTable[k] = objectNode.model.raw + /** Remove the instance from the list (if needed) */ + delete this.instanceProxies[k] + /** Remove the node from the world tree */ + this.tree.removeNode(objectNode, true) + } + + /** Remaining instance proxies should all be valid */ + for (const k in this.instanceProxies) { + const node = this.instanceProxies[k] + /** Create the final instances */ + await this.convertToNode(node.model.raw, node) + } + } + private async PointcloudToNode(obj: SpeckleObject, node: TreeNode) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts index 229e5cdbc..bff206219 100644 --- a/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts +++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts @@ -70,6 +70,8 @@ export class SpeckleGeometryConverter extends GeometryConverter { return this.TextToGeometryData(node) case SpeckleType.Transform: return this.TransformToGeometryData(node) + case SpeckleType.InstanceProxy: + return this.InstanceProxyToGeometyData(node) case SpeckleType.Unknown: // console.warn(`Skipping geometry conversion for ${type}`) return null @@ -176,6 +178,11 @@ export class SpeckleGeometryConverter extends GeometryConverter { return null } + private InstanceProxyToGeometyData(node: NodeData): GeometryData | null { + node + return null + } + /** * POINT CLOUD */ diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts index 398079f2c..ac01ffa64 100644 --- a/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts +++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts @@ -128,6 +128,8 @@ export class SpeckleLoader extends Loader { return Promise.resolve(false) } + await this.converter.convertInstances() + const t0 = performance.now() const geometryConverter = new SpeckleGeometryConverter() diff --git a/packages/viewer/src/modules/tree/WorldTree.ts b/packages/viewer/src/modules/tree/WorldTree.ts index 9bc101965..946516a5c 100644 --- a/packages/viewer/src/modules/tree/WorldTree.ts +++ b/packages/viewer/src/modules/tree/WorldTree.ts @@ -108,8 +108,14 @@ export class WorldTree { if (this.nodeMaps[parent.model.subtreeId]?.addNode(node)) parent.addChild(node) } - public removeNode(node: TreeNode): void { + public removeNode(node: TreeNode, removeChildren: boolean): void { + const children = node.children + this.nodeMaps[node.model.subtreeId]?.removeNode(node) node.drop() + if (!removeChildren || !children) return + for (let k = 0; k < children.length; k++) { + this.removeNode(children[k], removeChildren) + } } public findAll(predicate: SearchPredicate, node?: TreeNode): Array { @@ -197,7 +203,8 @@ export class WorldTree { if (subtreeNode) { this.nodeMaps[subtreeNode[0].model.subtreeId].purge() delete this.nodeMaps[subtreeNode[0].model.subtreeId] - this.removeNode(subtreeNode[0]) + // Potentially true? + this.removeNode(subtreeNode[0], false) } return }