From 627eb50ec9535edfe55475e5589e8d15f9ede558 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Sat, 6 Aug 2022 21:30:46 +0300 Subject: [PATCH] feat(filtering): wip colors --- packages/viewer-sandbox/src/Sandbox.ts | 13 +- packages/viewer/package.json | 2 +- .../viewer/src/modules/FilteringManager.ts | 141 +++++++++++++++--- packages/viewer/src/modules/Viewer.ts | 49 ++---- .../viewer/src/modules/materials/Materials.ts | 96 +++++------- yarn.lock | 9 +- 6 files changed, 198 insertions(+), 112 deletions(-) diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index 27c6d60f5..21b42a920 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -151,8 +151,19 @@ export default class Sandbox { const darkModeToggle = this.tabs.pages[0].addButton({ title: '🌞 / 🌛' }) + const dark = localStorage.getItem('dark') === 'dark' + if (dark) { + const dark = document + .getElementById('renderer') + ?.classList.toggle('background-dark') + } + darkModeToggle.on('click', () => { - document.getElementById('renderer')?.classList.toggle('background-dark') + const dark = document + .getElementById('renderer') + ?.classList.toggle('background-dark') + + localStorage.setItem('dark', dark ? `dark` : `light`) }) } diff --git a/packages/viewer/package.json b/packages/viewer/package.json index b603bb8ac..63655984f 100644 --- a/packages/viewer/package.json +++ b/packages/viewer/package.json @@ -43,7 +43,7 @@ "@speckle/objectloader": "workspace:^", "camera-controls": "^1.33.1", "hold-event": "^0.1.0", - "lodash.debounce": "^4.0.8", + "lodash-es": "^4.17.21", "rainbowvis.js": "^1.0.1", "three": "^0.140.0", "tree-model": "1.0.7" diff --git a/packages/viewer/src/modules/FilteringManager.ts b/packages/viewer/src/modules/FilteringManager.ts index 4fb204cbd..a2c13685a 100644 --- a/packages/viewer/src/modules/FilteringManager.ts +++ b/packages/viewer/src/modules/FilteringManager.ts @@ -1,4 +1,5 @@ -import { Color, Texture } from 'three' +import { get } from 'lodash' +import { Color, Texture, MathUtils } from 'three' import { TreeNode, WorldTree } from './tree/WorldTree' export enum FilterMaterialType { @@ -27,10 +28,12 @@ enum IsolateCommand { } export class FilteringManager { + private viewer: any private renderer: any - constructor(renderer: any) { - this.renderer = renderer + constructor(viewer: any) { + this.viewer = viewer + this.renderer = viewer.speckleRenderer } private setFilters() { @@ -86,14 +89,52 @@ export class FilteringManager { } } + /** + * Hides a bunch of objects. The opposite of `showObjects`. + * @param objectIds objects to hide. + * @param filterKey the "ui scope" this command is coming from. + * @param resourceUrl the resource url to limit searching to. + * @param ghost whether to ghost instead of completely hide the objects. + * @returns the current applied filter state. + */ public hideObjects(objectIds: string[], filterKey: string = null, resourceUrl: string = null, ghost = false) { return this.toggleObjectsVisibility(objectIds, VisibilityCommand.HIDE, filterKey, resourceUrl, ghost) } + /** + * Shows a bunch of objects. The opposite of `hideObjects`. + * @param objectIds objects to hide. + * @param filterKey the "ui scope" this command is coming from. + * @param resourceUrl the resource url to limit searching to. + * @returns the current applied filter state. + */ public showObjects(objectIds: string[], filterKey: string = null, resourceUrl: string = null) { return this.toggleObjectsVisibility(objectIds, VisibilityCommand.SHOW, filterKey, resourceUrl) } + /** + * Hides all the descendants of the provided object's id. The opposite of `showTree`. + * @param objectId the root object id. + * @param resourceUrl the resource url to limit searching to. + * @param ghost whether to ghost instead of completely hide the objects. + * @returns the current applied filter state. + */ + public hideTree(objectId: string, resourceUrl: string = null, ghost = false) { + const ids = this.getDescendantIds(objectId) + return this.hideObjects(ids, null, resourceUrl, ghost) + } + + /** + * Shows all the descendants of the provided object's id. The opposite of `hideTree`. + * @param objectId the root object id. + * @param resourceUrl the resource url to limit searching to. + * @returns the current applied filter state. + */ + public showTree(objectId: string, resourceUrl: string = null) { + const ids = this.getDescendantIds(objectId) + return this.showObjects(ids, null, resourceUrl) + } + private toggleObjectsVisibility( objectIds: string[], command = VisibilityCommand.HIDE, @@ -162,13 +203,56 @@ export class FilteringManager { } } + /** + * Isolates a bunch of objects - all other objects in the scene, besides the ones provided, are ghosted or hidden. The opposite of `unIsolateObjects`. + * @param objectIds objects to isolate. + * @param filterKey the "ui scope" this command is coming from. + * @param resourceUrl the resource url to limit searching to. + * @param ghost whether to ghost instead of completely hide the objects. + * @returns the current applied filter state. + */ public isolateObjects(objectIds: string[], filterKey: string = null, resourceUrl: string = null, ghost = true) { return this.toggleObjectsIsolation(objectIds, IsolateCommand.ISOLATE, filterKey, resourceUrl, ghost) } + + /** + * Unisolates a bunch of objects - if previously isolated, the provided objects will be either hidden or ghosted. The opposite of `isolateObjects`. + * @param objectIds objects to unisolate. + * @param filterKey the "ui scope" this command is coming from. + * @param resourceUrl the resource url to limit searching to. + * @param ghost whether to ghost instead of completely hide the objects. + * @returns the current applied filter state. + */ public unIsolateObjects(objectIds: string[], filterKey: string = null, resourceUrl: string = null) { return this.toggleObjectsIsolation(objectIds, IsolateCommand.UNISOLATE, filterKey, resourceUrl) } + /** + * Isolates the descendants of the provided object. All other objects in the scene, besides the descendants of the one provided, are ghosted or hidden. The opposite of `unIsolateTree`. + * @param objectId the parent object's id. + * @param filterKey the "ui scope" this command is coming from. + * @param resourceUrl the resource url to limit searching to. + * @param ghost whether to ghost instead of completely hide the objects. + * @returns the current applied filter state. + */ + public isolateTree(objectId: string, resourceUrl: string = null, ghost = true) { + const ids = this.getDescendantIds(objectId) + return this.isolateObjects(ids, null, resourceUrl, ghost) + } + + /** + * Unisolates the descendants of the provided object. All other objects in the scene, besides the descendants of the one provided, are ghosted or hidden. The opposite of `isolateTree`. + * @param objectId the parent object's id. + * @param filterKey the "ui scope" this command is coming from. + * @param resourceUrl the resource url to limit searching to. + * @param ghost whether to ghost instead of completely hide the objects. + * @returns the current applied filter state. + */ + public unIsolateTree(objectId: string, resourceUrl: string = null) { + const ids = this.getDescendantIds(objectId) + return this.unIsolateObjects(ids, null, resourceUrl) + } + private toggleObjectsIsolation( objectIds: string[], command = IsolateCommand.ISOLATE, @@ -210,24 +294,43 @@ export class FilteringManager { return this.setFilters() } - public showTree(objectId: string, resourceUrl: string = null) { - const ids = this.getDescendantIds(objectId) - return this.showObjects(ids, null, resourceUrl) + public setColorFilter(property: any, resourceUrl: string = null) { + if (property.type === 'numeric') { + // do something + } + if (property.type === 'string') { + // do something else + console.log(Object.keys(property.uniqueValues)) + const keys = Object.keys(property.uniqueValues) + + const colors: { value: string; color: any; rvs: any[] }[] = [] + + for (const key of keys) { + colors.push({ + color: new Color(MathUtils.randInt(0, 0xffffff)).getHex(), + value: key, + rvs: [] + }) + } + + WorldTree.getInstance().walk((node: TreeNode) => { + if (!node.model.atomic) return true + + const propertyValue = get(node.model.raw, property.key, null) + if (!propertyValue) return true + + const colorData = colors.find((c) => c.value === propertyValue) + if (!colorData) return true + + const rvs = WorldTree.getRenderTree(resourceUrl).getRenderViewsForNode(node) + colorData.rvs.push(...rvs) + }) + console.log(colors) + } } - public hideTree(objectId: string, resourceUrl: string = null) { - const ids = this.getDescendantIds(objectId) - return this.hideObjects(ids, null, resourceUrl) - } - - public isolateTree(objectId: string, resourceUrl: string = null, ghost = true) { - const ids = this.getDescendantIds(objectId) - return this.isolateObjects(ids, null, resourceUrl, ghost) - } - - public unIsolateTree(objectId: string, resourceUrl: string = null, ghost = true) { - const ids = this.getDescendantIds(objectId) - return this.unIsolateObjects(ids, null, resourceUrl, ghost) + public removeColorFilter() { + // TODO } private lookupCache = {} diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index d3041d9ac..1b0453f0e 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -104,9 +104,11 @@ export class Viewer extends EventEmitter implements IViewer { console.warn('Built stuff') }) - this.FilterManager = new FilteringManager(this.speckleRenderer) - ;(window as any).WorldTree = WorldTree + this.FilterManager = new FilteringManager(this) + ;(window as any).WT = WorldTree ;(window as any).FilterManager = this.FilterManager + ;(window as any).FM = this.FilterManager + ;(window as any).R = this } public async init(): Promise { @@ -261,8 +263,8 @@ export class Viewer extends EventEmitter implements IViewer { WorldTree.getInstance().walk((node: TreeNode) => { if (!node.model.atomic) return true - console.log(node) const obj = flattenObject(node.model.raw) + for (const prop of Object.keys(obj)) { if (!(prop in propValues)) { propValues[prop] = [] @@ -272,7 +274,7 @@ export class Viewer extends EventEmitter implements IViewer { return true }) - const propInfo = {} + const propInfo = [] for (const prop in propValues) { const pinfo = { type: typeof propValues[prop][0], @@ -280,7 +282,8 @@ export class Viewer extends EventEmitter implements IViewer { allValues: propValues[prop], uniqueValues: {}, minValue: propValues[prop][0], - maxValue: propValues[prop][0] + maxValue: propValues[prop][0], + key: null as string } for (const v of propValues[prop]) { if (v < pinfo.minValue) pinfo.minValue = v @@ -290,9 +293,11 @@ export class Viewer extends EventEmitter implements IViewer { } pinfo.uniqueValues[v] += 1 } - - propInfo[prop] = pinfo + pinfo.key = prop + propInfo.push(pinfo) } + + return propInfo } public debugGetFilterByNumericPropetyData(propertyName: string): { @@ -398,9 +403,11 @@ export class Viewer extends EventEmitter implements IViewer { const rgb = new Color(`hsl(${colorHue}, 50%, 30%)`) return rgb.getHex() } + const data: { color?: { name: string; color: string; colorIndex: number; nodes: [] } } = {} + let colorCount = 0 /** This is the lazy approach */ WorldTree.getInstance().walk((node: TreeNode) => { @@ -453,34 +460,6 @@ export class Viewer extends EventEmitter implements IViewer { console.warn(`Filter time: ${performance.now() - start}`) } - // private isObject(value) { - // return !!(value && typeof value === 'object' && !Array.isArray(value)) - // } - - // private findObjectProperty(object = {}, keyToMatch = '') { - // if (this.isObject(object)) { - // const entries = Object.entries(object) - - // for (let i = 0; i < entries.length; i += 1) { - // const [objectKey, objectValue] = entries[i] - - // if (objectKey === keyToMatch) { - // return object[objectKey] - // } - - // if (this.isObject(objectValue)) { - // const child = this.findObjectProperty(objectValue, keyToMatch) - - // if (child !== null) { - // return child - // } - // } - // } - // } - - // return null - // } - public dispose() { // TODO: currently it's easier to simply refresh the page :) } diff --git a/packages/viewer/src/modules/materials/Materials.ts b/packages/viewer/src/modules/materials/Materials.ts index 846816344..97301bb41 100644 --- a/packages/viewer/src/modules/materials/Materials.ts +++ b/packages/viewer/src/modules/materials/Materials.ts @@ -41,9 +41,7 @@ export default class Materials { renderMaterial = { id: node.model.raw.renderMaterial.id, color: node.model.raw.renderMaterial.diffuse, - opacity: node.model.raw.renderMaterial.opacity - ? node.model.raw.renderMaterial.opacity - : 1, + opacity: node.model.raw.renderMaterial.opacity ? node.model.raw.renderMaterial.opacity : 1, vertexColors: node.model.raw.colors && node.model.raw.colors.length > 0 } } @@ -222,31 +220,29 @@ export default class Materials { ;(this.lineHiddenMaterial).resolution = new Vector2() this.lineHiddenMaterial.visible = false - this.materialMap[NodeRenderView.NullRenderMaterialHash] = - new SpeckleStandardMaterial( - { - color: 0x7f7f7f, - emissive: 0x0, - roughness: 1, - metalness: 0, - side: DoubleSide // TBD, - // clippingPlanes: this.viewer.sectionBox.planes - }, - ['USE_RTE'] - ) - this.materialMap[NodeRenderView.NullRenderMaterialVertexColorsHash] = - new SpeckleStandardMaterial( - { - color: 0x7f7f7f, - emissive: 0x0, - roughness: 1, - metalness: 0, - side: DoubleSide, // TBD - vertexColors: true - // clippingPlanes: this.viewer.sectionBox.planes - }, - ['USE_RTE'] - ) + this.materialMap[NodeRenderView.NullRenderMaterialHash] = new SpeckleStandardMaterial( + { + color: 0x7f7f7f, + emissive: 0x0, + roughness: 1, + metalness: 0, + side: DoubleSide // TBD, + // clippingPlanes: this.viewer.sectionBox.planes + }, + ['USE_RTE'] + ) + this.materialMap[NodeRenderView.NullRenderMaterialVertexColorsHash] = new SpeckleStandardMaterial( + { + color: 0x7f7f7f, + emissive: 0x0, + roughness: 1, + metalness: 0, + side: DoubleSide, // TBD + vertexColors: true + // clippingPlanes: this.viewer.sectionBox.planes + }, + ['USE_RTE'] + ) const hash = NodeRenderView.NullDisplayStyleHash // So prettier doesn't fuck up everything this.materialMap[hash] = new SpeckleLineMaterial({ @@ -272,22 +268,20 @@ export default class Materials { sizeAttenuation: false // clippingPlanes: this.viewer.sectionBox.planes }) - this.materialMap[NodeRenderView.NullPointCloudVertexColorsMaterialHash] = - new SpecklePointMaterial({ - color: 0xffffff, - vertexColors: true, - size: 2, - sizeAttenuation: false - // clippingPlanes: this.viewer.sectionBox.planes - }) - this.materialMap[NodeRenderView.NullPointCloudMaterialHash] = - new SpecklePointMaterial({ - color: 0xffffff, - vertexColors: false, - size: 2, - sizeAttenuation: false - // clippingPlanes: this.viewer.sectionBox.planes - }) + this.materialMap[NodeRenderView.NullPointCloudVertexColorsMaterialHash] = new SpecklePointMaterial({ + color: 0xffffff, + vertexColors: true, + size: 2, + sizeAttenuation: false + // clippingPlanes: this.viewer.sectionBox.planes + }) + this.materialMap[NodeRenderView.NullPointCloudMaterialHash] = new SpecklePointMaterial({ + color: 0xffffff, + vertexColors: false, + size: 2, + sizeAttenuation: false + // clippingPlanes: this.viewer.sectionBox.planes + }) } private makeMeshMaterial(materialData: RenderMaterial): Material { @@ -344,11 +338,7 @@ export default class Materials { return mat } - public updateMaterialMap( - hash: number, - material: RenderMaterial | DisplayStyle, - type: GeometryType - ): Material { + public updateMaterialMap(hash: number, material: RenderMaterial | DisplayStyle, type: GeometryType): Material { // console.log(this.materialMap) if (this.materialMap[hash]) { // console.warn(`Duplicate material hash found: ${hash}`) @@ -488,10 +478,7 @@ export default class Materials { } } - public getFilterMaterial( - renderView: NodeRenderView, - filterMaterial: FilterMaterialType - ) { + public getFilterMaterial(renderView: NodeRenderView, filterMaterial: FilterMaterialType) { switch (filterMaterial) { case FilterMaterialType.SELECT: return this.getHighlightMaterial(renderView) @@ -508,8 +495,7 @@ export default class Materials { public getFilterMaterialOptions(filterMaterial: FilterMaterial) { return { - rampIndex: - filterMaterial.rampIndex !== undefined ? filterMaterial.rampIndex : undefined, + rampIndex: filterMaterial.rampIndex !== undefined ? filterMaterial.rampIndex : undefined, rampIndexColor: filterMaterial.rampIndexColor, rampTexture: filterMaterial.rampTexture ? filterMaterial.rampTexture : undefined } diff --git a/yarn.lock b/yarn.lock index 4665c1aef..756696659 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5088,7 +5088,7 @@ __metadata: eslint: ^8.11.0 eslint-config-prettier: ^8.5.0 hold-event: ^0.1.0 - lodash.debounce: ^4.0.8 + lodash-es: ^4.17.21 prettier: ^2.5.1 rainbowvis.js: ^1.0.1 regenerator-runtime: ^0.13.7 @@ -18163,6 +18163,13 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 + languageName: node + linkType: hard + "lodash-webpack-plugin@npm:^0.11.6": version: 0.11.6 resolution: "lodash-webpack-plugin@npm:0.11.6"