From e0db231156d776490173fdeb3cd645bcb2005d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Gonz=C3=A1lez=20Viegas?= Date: Wed, 15 May 2024 22:46:49 +0200 Subject: [PATCH] feat: add minimap tutorial --- .../src/core/Materials/example.html | 34 -- .../components/src/core/Materials/example.ts | 225 ----------- .../components/src/core/Materials/index.ts | 122 ------ .../components/src/core/MiniMap/example.html | 9 + .../components/src/core/MiniMap/example.ts | 154 +++++--- packages/components/src/core/MiniMap/index.ts | 18 +- .../components/src/core/MiniMap/src/index.ts | 8 +- packages/components/src/core/index.ts | 1 - .../src/ifc/IfcPropertiesFinder/index.ts | 365 ------------------ .../src/ifc/IfcPropertiesFinder/src/types.ts | 36 -- packages/components/src/ifc/index.ts | 1 - 11 files changed, 110 insertions(+), 863 deletions(-) delete mode 100644 packages/components/src/core/Materials/example.html delete mode 100644 packages/components/src/core/Materials/example.ts delete mode 100644 packages/components/src/core/Materials/index.ts delete mode 100644 packages/components/src/ifc/IfcPropertiesFinder/index.ts delete mode 100644 packages/components/src/ifc/IfcPropertiesFinder/src/types.ts diff --git a/packages/components/src/core/Materials/example.html b/packages/components/src/core/Materials/example.html deleted file mode 100644 index ac8e22e2d..000000000 --- a/packages/components/src/core/Materials/example.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - Materials - - - - -
- - - - \ No newline at end of file diff --git a/packages/components/src/core/Materials/example.ts b/packages/components/src/core/Materials/example.ts deleted file mode 100644 index 3ca48b892..000000000 --- a/packages/components/src/core/Materials/example.ts +++ /dev/null @@ -1,225 +0,0 @@ -/* eslint import/no-extraneous-dependencies: 0 */ - -import * as THREE from "three"; -// eslint-disable-next-line import/no-extraneous-dependencies -import Stats from "stats.js"; -import * as BUI from "@thatopen/ui"; -import * as OBC from "../.."; - -const container = document.getElementById("container")!; - -const components = new OBC.Components(); - -const worlds = components.get(OBC.Worlds); -const world = worlds.create< - OBC.SimpleScene, - OBC.SimpleCamera, - OBC.SimpleRenderer ->(); - -world.scene = new OBC.SimpleScene(components); -world.renderer = new OBC.SimpleRenderer(components, container); -world.camera = new OBC.SimpleCamera(components); - -components.init(); - -world.camera.controls.setLookAt(13, 13, 13, 0, 0, 0); - -world.scene.setup(); - -/* MD - ### ๐Ÿ‘จโ€๐ŸŽจ Seamless Material Control - --- - - Have you ever had trouble handling multiple **Materials** for your BIM project? - You may be asking why different **Materials** are required. Sometimes you need to highlight a mesh - or group the meshes, and render them with different materials; the possibilities are limitless.๐Ÿ’ซ - -:::tip First, let's set up a simple scene! - - ๐Ÿ‘€ If you haven't started there, check out [that tutorial first](SimpleScene.mdx)! - - ::: - - In this tutorial, we use **MaterialManager** to control materials for various meshes. - We will also show you how to adjust the background of your scene.๐ŸŒ— - - - ### ๐Ÿงฑ Creating Multiple Meshes - --- - - Now that our scene setup is done, let's add 3D elements to our scene as well. - We will add [Box Geometry](https://threejs.org/docs/#api/en/geometries/BoxGeometry) - and [Sphere Geometry](https://threejs.org/docs/#api/en/geometries/SphereGeometry) along with a material.๐Ÿ–Œ๏ธ - - #### Grouping the Meshes - - ๐Ÿ—ƒ๏ธ To make things easier to understand and use, we will categorize and store meshes into **`cubes[]`** and **`spheres[]`**. - - */ - -const cubes: THREE.Mesh[] = []; -const spheres: THREE.Mesh[] = []; - -const boxGeometry = new THREE.BoxGeometry(2, 2, 2); -const sphereGeometry = new THREE.SphereGeometry(2, 8, 8); -const material = new THREE.MeshLambertMaterial({ color: "#6528D7" }); - -/* MD - - #### Changing Positions - - We have made preparations for adding mesh to our scene; but, if we do not alter their placements, - all the meshes will be rendered in the same location, which is `(0,0,0)`.๐Ÿ“ - - :::info Randomising the Cube Placement - - We'll write a quick **utility** function that returns a random number between 0 and the specified upper limit. - You can use this for a variety of purposes, but for this tutorial - it will be used to generate random positions for cube and sphere placement.๐Ÿ“Œ - - ::: - - */ -function getRandomNumber(limit: number) { - return Math.random() * limit; -} - -/* MD - - ### ๐Ÿงช Generating Cubes and Spheres - - Now, we will write a simple **`for loop`** which will generate **3 Cubes** and **3 Sphere** meshes. - In addition, we will use the `getRandomNumber` function that we defined before and add the meshes to the scene with updated positions.๐Ÿ”ฎ - - */ - -function generateCubes() { - for (let i = 0; i < 3; i++) { - const cube = new THREE.Mesh(boxGeometry, material); - cube.position.x = getRandomNumber(10); - cube.position.y = getRandomNumber(10); - cube.position.z = getRandomNumber(10); - cube.updateMatrix(); - world.scene.three.add(cube); - cubes.push(cube); - } -} - -function generateSpheres() { - for (let i = 0; i < 3; i++) { - const sphere = new THREE.Mesh(sphereGeometry, material); - sphere.position.x = getRandomNumber(10); - sphere.position.y = getRandomNumber(10); - sphere.position.z = getRandomNumber(10); - sphere.updateMatrix(); - world.scene.three.add(sphere); - spheres.push(sphere); - } -} - -generateCubes(); -generateSpheres(); - -/* MD - - ### ๐ŸŽจ Materials Made Easy - --- - - Here comes the interesting part! We will now use **[Material Manager](../api/classes/components.MaterialManager)** - for manipulating the materials and background. Let's create different materials for Cubes and Spheres.๐ŸŽญ - - */ -const materials = components.get(OBC.Materials); - -const cubeColor = new THREE.Color(0x71d728); -const cubeMaterial = new THREE.MeshBasicMaterial({ color: cubeColor }); - -const sphereColor = new THREE.Color(0xd7286b); -const sphereMaterial = new THREE.MeshBasicMaterial({ color: sphereColor }); - -/* MD - - :::tip Syncing Components with your own theme! - - ๐Ÿ”ฅ You may have noticed that we are also implementing `backgroundColor`, - which will allow you to modify the background of your scene with a single call! - - With components, you can now modify the background of your BIM app to match the theme you're using. - - ::: - - #### Storing the Material with Manager - - Now that the materials have been created, we will `name` them and use **`addMaterial`** to store them in `materialManager`. - - Next, we'll add the necessary meshes to help **materialManager** to choose which mesh to use when applying materials.๐Ÿงฎ - -*/ -materials.addMaterial("cubeMaterial", cubeMaterial); -materials.addMeshes("cubeMaterial", cubes); - -materials.addMaterial("sphereMaterial", sphereMaterial); -materials.addMeshes("sphereMaterial", spheres); - -/* MD - ### ๐Ÿšฆ Controlling the Manager Events - --- - - With all the things in place, we will use `dat.GUI` for controlling the materials! - - - `materialManager.set(boolean, ["materialName", ..])` - You can use this API to change the active material for a mesh group. - - `materialManager.setBackgroundColor(Color) - This API will help you to change the background of your scene. - - */ - -BUI.Manager.registerComponents(); - -const panel = BUI.Component.create(() => { - return BUI.html` - - - - - - - - - - - - - - - `; -}); - -document.body.append(panel); - -/* MD - - Great job! ๐ŸŽ‰ Now you know how to manage multiple materials in your app using - **[Material Manager](../api/classes/components.MaterialManager)** component! ๐Ÿ’ช - Your BIM software can be made more visually appealing and aesthetically pleasing. - Let's keep it up and check out another tutorials! - - */ - -const stats = new Stats(); -stats.showPanel(2); -document.body.append(stats.dom); -stats.dom.style.left = "0px"; -world.renderer.onBeforeUpdate.add(() => stats.begin()); -world.renderer.onAfterUpdate.add(() => stats.end()); diff --git a/packages/components/src/core/Materials/index.ts b/packages/components/src/core/Materials/index.ts deleted file mode 100644 index 453e62198..000000000 --- a/packages/components/src/core/Materials/index.ts +++ /dev/null @@ -1,122 +0,0 @@ -import * as THREE from "three"; -import { Component, Disposable, Event } from "../Types"; -import { Components } from "../Components"; - -// TODO: Clean up and document -// TODO: Disable / enable instance color for instance meshes - -/** - * A tool to easily handle the materials of massive amounts of - * objects and scene background easily. - */ -export class Materials extends Component implements Disposable { - static readonly uuid = "24989d27-fa2f-4797-8b08-35918f74e502" as const; - - /** {@link Component.enabled} */ - enabled = true; - - /** {@link Disposable.onDisposed} */ - readonly onDisposed = new Event(); - - list: { - [id: string]: { - material: THREE.Material; - meshes: Set; - }; - } = {}; - - private _originals: { - [guid: string]: { - material: THREE.Material[] | THREE.Material; - instances?: any; - }; - } = {}; - - constructor(components: Components) { - super(components); - this.components.add(Materials.uuid, this); - } - - /** {@link Disposable.dispose} */ - dispose() { - for (const id in this.list) { - const { material } = this.list[id]; - material.dispose(); - } - this.list = {}; - this._originals = {}; - this.onDisposed.trigger(Materials.uuid); - this.onDisposed.reset(); - } - - /** - * Turns the specified material styles on or off. - * - * @param active whether to turn it on or off. - * @param ids the ids of the style to turn on or off. - */ - set(active: boolean, ids = Object.keys(this.list)) { - for (const id of ids) { - const { material, meshes } = this.list[id]; - for (const mesh of meshes) { - if (active) { - if (!this._originals[mesh.uuid]) { - this._originals[mesh.uuid] = { material: mesh.material }; - } - if (mesh instanceof THREE.InstancedMesh && mesh.instanceColor) { - this._originals[mesh.uuid].instances = mesh.instanceColor; - mesh.instanceColor = null; - } - mesh.material = material; - } else { - if (!this._originals[mesh.uuid]) continue; - mesh.material = this._originals[mesh.uuid].material; - const instances = this._originals[mesh.uuid].instances; - if (mesh instanceof THREE.InstancedMesh && instances) { - mesh.instanceColor = instances; - } - } - } - } - } - - /** - * Creates a new material style. - * @param id the identifier of the style to create. - * @param material the material of the style. - */ - addMaterial(id: string, material: THREE.Material) { - if (this.list[id]) { - throw new Error("This ID already exists!"); - } - this.list[id] = { material, meshes: new Set() }; - } - - /** - * Assign meshes to a certain style. - * @param id the identifier of the style. - * @param meshes the meshes to assign to the style. - */ - addMeshes(id: string, meshes: THREE.Mesh[]) { - if (!this.list[id]) { - throw new Error("This ID doesn't exists!"); - } - for (const mesh of meshes) { - this.list[id].meshes.add(mesh); - } - } - - /** - * Remove meshes from a certain style. - * @param id the identifier of the style. - * @param meshes the meshes to assign to the style. - */ - removeMeshes(id: string, meshes: THREE.Mesh[]) { - if (!this.list[id]) { - throw new Error("This ID doesn't exists!"); - } - for (const mesh of meshes) { - this.list[id].meshes.delete(mesh); - } - } -} diff --git a/packages/components/src/core/MiniMap/example.html b/packages/components/src/core/MiniMap/example.html index 26dc81ecf..137255cf8 100644 --- a/packages/components/src/core/MiniMap/example.html +++ b/packages/components/src/core/MiniMap/example.html @@ -13,6 +13,7 @@ body { margin: 0; padding: 0; + font-family: "Plus Jakarta Sans", sans-serif; } .full-screen { @@ -21,11 +22,19 @@ position: relative; overflow: hidden; } + + .minimap { + position: fixed; + z-index: 999; + bottom: 1rem; + right: 1rem; + }
+
diff --git a/packages/components/src/core/MiniMap/example.ts b/packages/components/src/core/MiniMap/example.ts index 388027c2e..b3063a7c3 100644 --- a/packages/components/src/core/MiniMap/example.ts +++ b/packages/components/src/core/MiniMap/example.ts @@ -1,47 +1,33 @@ -// Set up scene (see SimpleScene tutorial) +/* eslint import/no-extraneous-dependencies: 0 */ -import * as THREE from "three"; import Stats from "stats.js"; -// @ts-ignore -import * as dat from "three/examples/jsm/libs/lil-gui.module.min"; +import * as BUI from "@thatopen/ui"; +import * as THREE from "three"; import * as OBC from "../.."; const container = document.getElementById("container")!; const components = new OBC.Components(); -const sceneComponent = new OBC.SimpleScene(components); -sceneComponent.setup(); -components.scene = sceneComponent; +const worlds = components.get(OBC.Worlds); +const world = worlds.create< + OBC.SimpleScene, + OBC.SimpleCamera, + OBC.SimpleRenderer +>(); -const rendererComponent = new OBC.PostproductionRenderer(components, container); -components.renderer = rendererComponent; +world.scene = new OBC.SimpleScene(components); +world.renderer = new OBC.SimpleRenderer(components, container); +world.camera = new OBC.SimpleCamera(components); -const cameraComponent = new OBC.SimpleCamera(components); -components.camera = cameraComponent; - -components.raycaster = new OBC.SimpleRaycaster(components); +world.scene.setup(); components.init(); -rendererComponent.postproduction.enabled = true; - -const scene = components.scene.get(); +const grids = components.get(OBC.Grids); +grids.create(world); -cameraComponent.controls.setLookAt(10, 10, 10, 0, 0, 0); - -const directionalLight = new THREE.DirectionalLight(); -directionalLight.position.set(5, 10, 3); -directionalLight.intensity = 0.5; -scene.add(directionalLight); - -const ambientLight = new THREE.AmbientLight(); -ambientLight.intensity = 0.5; -scene.add(ambientLight); - -const grid = new OBC.SimpleGrid(components, new THREE.Color(0x666666)); -const gridMesh = grid.get(); -rendererComponent.postproduction.customEffects.excludedMeshes.push(gridMesh); +world.camera.controls.setLookAt(1, 2, -2, -2, 0, -5); /* MD ### ๐Ÿ‚ Navigate through BIM like Pro! @@ -75,10 +61,11 @@ rendererComponent.postproduction.customEffects.excludedMeshes.push(gridMesh); const fragments = new OBC.FragmentManager(components); -const file = await fetch("../../../resources/small.frag"); +const file = await fetch("../../../../../resources/small.frag"); const dataBlob = await file.arrayBuffer(); const buffer = new Uint8Array(dataBlob); -fragments.load(buffer); +const model = fragments.load(buffer); +world.scene.three.add(model); /* MD @@ -98,8 +85,13 @@ fragments.load(buffer); */ -const map = new OBC.MiniMap(components); -components.ui.add(map.uiElement.get("canvas")); +const maps = new OBC.MiniMaps(components); +const map = maps.create(world); + +const mapContainer = document.getElementById("minimap") as HTMLDivElement; +const canvas = map.renderer.domElement; +canvas.style.borderRadius = "12px"; +mapContainer.append(canvas); /* MD @@ -128,31 +120,69 @@ const stats = new Stats(); stats.showPanel(2); document.body.append(stats.dom); stats.dom.style.left = "0px"; -rendererComponent.onBeforeUpdate.add(() => stats.begin()); -rendererComponent.onAfterUpdate.add(() => stats.end()); - -// gui - -const gui = new dat.GUI(); - -const size = map.getSize(); - -gui.add(map, "enabled").name("Map enabled"); -gui.add(map, "lockRotation").name("Lock rotation"); -gui.add(map, "zoom").name("Zoom").min(0.01).max(0.5).step(0.01); -gui.add(map, "frontOffset").name("Front offset").min(0).max(10).step(0.5); -gui - .add(size, "x") - .name("Width") - .min(100) - .max(500) - .step(10) - .onChange(() => map.resize(size)); -gui - .add(size, "y") - .name("Height") - .min(100) - .max(500) - .step(10) - .onChange(() => map.resize(size)); -gui.addColor(map, "backgroundColor"); +world.renderer.onBeforeUpdate.add(() => stats.begin()); +world.renderer.onAfterUpdate.add(() => stats.end()); + +BUI.Manager.registerComponents(); + +const mapSize = map.getSize(); + +const panel = BUI.Component.create(() => { + return BUI.html` + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + +
+
+ `; +}); + +document.body.append(panel); diff --git a/packages/components/src/core/MiniMap/index.ts b/packages/components/src/core/MiniMap/index.ts index 9cd752e2b..9d517ecb0 100644 --- a/packages/components/src/core/MiniMap/index.ts +++ b/packages/components/src/core/MiniMap/index.ts @@ -1,12 +1,6 @@ -import { - Component, - Components, - Disposable, - Updateable, - World, - Event, -} from "../../core"; import { MiniMap } from "./src"; +import { Component, Updateable, World, Event, Disposable } from "../Types"; +import { Components } from "../Components"; export class MiniMaps extends Component implements Updateable, Disposable { static readonly uuid = "39ad6aad-84c8-4adf-a1e0-7f25313a9e7f" as const; @@ -43,8 +37,12 @@ export class MiniMaps extends Component implements Updateable, Disposable { this.list.delete(id); } - dispose(): void | Promise { - return undefined; + dispose() { + for (const [_id, map] of this.list) { + map.dispose(); + } + this.list.clear(); + this.onDisposed.trigger(); } update() { diff --git a/packages/components/src/core/MiniMap/src/index.ts b/packages/components/src/core/MiniMap/src/index.ts index 2def7be36..4adef4795 100644 --- a/packages/components/src/core/MiniMap/src/index.ts +++ b/packages/components/src/core/MiniMap/src/index.ts @@ -1,11 +1,5 @@ import * as THREE from "three"; -import { - Event, - Resizeable, - Updateable, - Disposable, - World, -} from "../../../core"; +import { Resizeable, Updateable, World, Event, Disposable } from "../../Types"; export class MiniMap implements Resizeable, Updateable, Disposable { /** {@link Disposable.onDisposed} */ diff --git a/packages/components/src/core/index.ts b/packages/components/src/core/index.ts index ff51882f4..c100866d0 100644 --- a/packages/components/src/core/index.ts +++ b/packages/components/src/core/index.ts @@ -5,7 +5,6 @@ export * from "./Types"; export * from "./Worlds"; export * from "./Grids"; export * from "./Clipper"; -export * from "./Materials"; export * from "./Cullers"; export * from "./MiniMap"; export * from "./OrthoPerspectiveCamera"; diff --git a/packages/components/src/ifc/IfcPropertiesFinder/index.ts b/packages/components/src/ifc/IfcPropertiesFinder/index.ts deleted file mode 100644 index 747b05fdd..000000000 --- a/packages/components/src/ifc/IfcPropertiesFinder/index.ts +++ /dev/null @@ -1,365 +0,0 @@ -import * as WEBIFC from "web-ifc"; -import * as FRAGS from "@thatopen/fragments"; -import { FragmentsGroup, IfcProperties } from "@thatopen/fragments"; -import { Disposable, Event, Component, Components } from "../../core"; -import { IfcPropertiesUtils } from "../Utils"; - -import { - QueryOperators, - QueryGroup, - AttributeQuery, - ConditionFunctions, -} from "./src/types"; -import { FragmentManager } from "../../fragments/FragmentManager"; - -// TODO: This works only for local properties, implement for streaming - -export interface QueryResult { - [modelID: string]: { - modelEntities: Set; - otherEntities: Set; - }; -} - -export interface IndexedModels { - [modelID: string]: { [expressID: number]: Set }; -} - -export class IfcPropertiesFinder extends Component implements Disposable { - readonly onFound = new Event(); - - /** {@link Disposable.onDisposed} */ - readonly onDisposed = new Event(); - - enabled: boolean = true; - - indexedModels: IndexedModels = {}; - - private readonly _conditionFunctions: ConditionFunctions; - private _noHandleAttributes = ["type"]; - - constructor(components: Components) { - super(components); - this._conditionFunctions = this.getConditionFunctions(); - const fragmentManager = components.get(FragmentManager); - fragmentManager.onFragmentsDisposed.add(this.onFragmentsDisposed); - } - - private onFragmentsDisposed = (data: { - groupID: string; - fragmentIDs: string[]; - }) => { - delete this.indexedModels[data.groupID]; - }; - - dispose() { - this.indexedModels = {}; - this.onFound.reset(); - this.onDisposed.trigger(); - this.onDisposed.reset(); - } - - private async indexEntityRelations(model: FragmentsGroup) { - const map: { [expressID: number]: Set } = {}; - - await IfcPropertiesUtils.getRelationMap( - model, - WEBIFC.IFCRELDEFINESBYPROPERTIES, - async (relatingID, relatedIDs) => { - if (!map[relatingID]) map[relatingID] = new Set(); - const props: number[] = []; - await IfcPropertiesUtils.getPsetProps(model, relatingID, (propID) => { - props.push(propID); - map[relatingID].add(propID); - if (!map[propID]) map[propID] = new Set(); - map[propID].add(relatingID); - }); - for (const relatedID of relatedIDs) { - map[relatingID].add(relatedID); - for (const propID of props) map[propID].add(relatedID); - if (!map[relatedID]) map[relatedID] = new Set(); - map[relatedID].add(relatedID); - } - }, - ); - - const ifcRelations = [ - WEBIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE, - WEBIFC.IFCRELDEFINESBYTYPE, - WEBIFC.IFCRELASSIGNSTOGROUP, - ]; - - for (const relation of ifcRelations) { - await IfcPropertiesUtils.getRelationMap( - model, - relation, - async (relatingID, relatedIDs) => { - if (!map[relatingID]) map[relatingID] = new Set(); - for (const relatedID of relatedIDs) { - map[relatingID].add(relatedID); - if (!map[relatedID]) map[relatedID] = new Set(); - map[relatedID].add(relatedID); - } - }, - ); - } - - this.indexedModels[model.uuid] = map; - return map; - } - - async find(groups: QueryGroup[], queryModels?: FragmentsGroup[]) { - const fragments = this.components.get(FragmentManager); - - const models = queryModels || fragments.groups.values(); - const result: QueryResult = {}; - - for (const model of models) { - let map = this.indexedModels[model.uuid]; - if (!map) { - map = await this.indexEntityRelations(model); - } - let relations: number[] = []; - for (const [index, group] of groups.entries()) { - const excludedItems = new Set(); - const groupResult = this.simpleQuery(model, group, excludedItems); - const groupRelations: number[] = []; - for (const expressID of groupResult) { - const relations = map[expressID]; - if (!relations) continue; - groupRelations.push(expressID); - for (const id of relations) { - if (!excludedItems.has(id)) { - groupRelations.push(id); - } - } - } - relations = - group.operator === "AND" && index > 0 - ? this.getCommonElements(relations, groupRelations) - : [...relations, ...groupRelations]; - } - - const modelEntities = new Set(); - - for (const [expressID] of model.data) { - const included = relations.includes(expressID); - if (included) { - modelEntities.add(expressID); - } - } - - const otherEntities = new Set(); - - for (const expressID of relations) { - const included = modelEntities.has(expressID); - if (included) continue; - otherEntities.add(expressID); - } - - result[model.uuid] = { modelEntities, otherEntities }; - } - - const foundFragments = this.toFragmentMap(result); - this.onFound.trigger(foundFragments); - return foundFragments; - } - - private toFragmentMap(data: QueryResult) { - const fragments = this.components.get(FragmentManager); - const fragmentMap: FRAGS.FragmentIdMap = {}; - for (const modelID in data) { - const model = fragments.groups.get(modelID); - if (!model) continue; - const matchingEntities = data[modelID].modelEntities; - for (const expressID of matchingEntities) { - const data = model.data.get(expressID); - if (!data) continue; - for (const key of data[0]) { - const fragmentID = model.keyFragments.get(key); - if (!fragmentID) { - throw new Error("Fragment ID not found!"); - } - if (!fragmentMap[fragmentID]) { - fragmentMap[fragmentID] = new Set(); - } - fragmentMap[fragmentID].add(expressID); - } - } - } - return fragmentMap; - } - - private simpleQuery( - model: FragmentsGroup, - queryGroup: QueryGroup, - excludedItems: Set, - ) { - const properties = model.getLocalProperties() as IfcProperties; - if (!properties) throw new Error("Model has no properties"); - let filteredProps: { - [expressID: number]: { [attributeName: string]: any }; - } = {}; - let iterations = 0; - let matchingEntities: number[] = []; - for (const query of queryGroup.queries) { - let queryResult: number[] = []; - const workingProps = - query.operator === "AND" ? filteredProps : properties; - const isAttributeQuery = (query as AttributeQuery).condition; // Is there a better way? - if (isAttributeQuery) { - const matchingResult = this.getMatchingEntities( - workingProps, - query as AttributeQuery, - excludedItems, - ); - queryResult = matchingResult.expressIDs; - filteredProps = { ...filteredProps, ...matchingResult.entities }; - } else { - queryResult = [ - ...this.simpleQuery(model, query as QueryGroup, excludedItems), - ]; - } - matchingEntities = - iterations === 0 - ? queryResult - : this.combineArrays( - matchingEntities, - queryResult, - query.operator ?? "AND", // Defaults to AND if iterations > 0 and query.operator is not defined - ); - iterations++; - } - return new Set(matchingEntities); - } - - private getMatchingEntities( - entities: { [expressID: number]: { [attributeName: string]: any } }, - query: AttributeQuery, - excludedItems: Set, - ) { - const { attribute: attributeName, condition } = query; - let { value } = query; - - const handleAttribute = !this._noHandleAttributes.includes(attributeName); - const expressIDs: number[] = []; - const matchingEntities: { - [expressID: number]: { [attributeName: string]: any }; - }[] = []; - for (const expressID in entities) { - const entity = entities[expressID]; - if (entity === undefined) { - continue; - } - const attribute = entity[attributeName]; - let attributeValue = handleAttribute ? attribute?.value : attribute; - if (attributeValue === undefined || attributeValue === null) continue; - - // TODO: Maybe the user can specify the value type in the finder menu, so we don't need this - const type1 = typeof value; - const type2 = typeof attributeValue; - if (type1 === "number" && type2 === "string") { - value = value.toString(); - } else if (type1 === "string" && type2 === "number") { - attributeValue = attributeValue.toString(); - } - - let conditionMatches = this._conditionFunctions[condition]( - attributeValue, - value, - ); - if (query.negateResult) { - conditionMatches = !conditionMatches; - } - if (!conditionMatches) { - if (query.negateResult) { - excludedItems.add(entity.expressID); - } - continue; - } - expressIDs.push(entity.expressID); - matchingEntities.push(entity); - } - return { expressIDs, entities: matchingEntities, excludedItems }; - } - - private combineArrays( - arrA: number[], - arrB: number[], - operator?: QueryOperators, - ) { - if (!operator) return arrB; - return operator === "AND" - ? this.arrayIntersection(arrA, arrB) - : this.arrayUnion(arrA, arrB); - } - - private getCommonElements(...lists: number[][]) { - const result: number[] = []; - - const elementsCount = new Map(); - - for (const list of lists) { - const uniqueElements = new Set(list); - for (const element of uniqueElements) { - if (elementsCount.has(element)) { - elementsCount.set(element, elementsCount.get(element)! + 1); - } else { - elementsCount.set(element, 1); - } - } - } - - for (const [element, count] of elementsCount) { - if (count === lists.length) { - result.push(element); - } - } - - return result; - } - - private arrayIntersection(arrA: number[], arrB: number[]) { - return arrA.filter((x) => arrB.includes(x)); - } - - private arrayUnion(arrA: number[], arrB: number[]) { - return [...arrA, ...arrB]; - } - - private getConditionFunctions() { - return { - is: ( - leftValue: string | boolean | number, - rightValue: string | boolean | number, - ) => { - return leftValue === rightValue; - }, - includes: ( - leftValue: string | boolean | number, - rightValue: string | boolean | number, - ) => { - return leftValue.toString().includes(rightValue.toString()); - }, - startsWith: ( - leftValue: string | boolean | number, - rightValue: string | boolean | number, - ) => { - return leftValue.toString().startsWith(rightValue.toString()); - }, - endsWith: ( - leftValue: string | boolean | number, - rightValue: string | boolean | number, - ) => { - return leftValue.toString().endsWith(rightValue.toString()); - }, - matches: ( - leftValue: string | boolean | number, - rightValue: string | boolean | number, - ) => { - const regex = new RegExp(rightValue.toString()); - return regex.test(leftValue.toString()); - }, - }; - } -} diff --git a/packages/components/src/ifc/IfcPropertiesFinder/src/types.ts b/packages/components/src/ifc/IfcPropertiesFinder/src/types.ts deleted file mode 100644 index 9fd69b24e..000000000 --- a/packages/components/src/ifc/IfcPropertiesFinder/src/types.ts +++ /dev/null @@ -1,36 +0,0 @@ -export type QueryConditions = - | "is" - | "includes" - | "startsWith" - | "endsWith" - | "matches"; - -export type ConditionFunctions = { - [queryCondition in QueryConditions]: ( - leftValue: string | boolean | number, - rightValue: string | boolean | number, - ) => boolean; -}; - -export type QueryOperators = "AND" | "OR"; - -export interface AttributeQuery { - operator?: QueryOperators; - attribute: string; - condition: QueryConditions; - value: string | number | boolean; - negateResult?: boolean; // Not working yet - caseSensitive?: boolean; // Not working yet -} - -export interface QueryGroup { - operator?: QueryOperators; - description?: string; - queries: (AttributeQuery | QueryGroup)[]; -} - -export interface Query { - name: string; - description?: string; - groups: QueryGroup; -} diff --git a/packages/components/src/ifc/index.ts b/packages/components/src/ifc/index.ts index 0ae71f632..38cc45ff0 100644 --- a/packages/components/src/ifc/index.ts +++ b/packages/components/src/ifc/index.ts @@ -1,5 +1,4 @@ export * from "./IfcJsonExporter"; -export * from "./IfcPropertiesFinder"; export * from "./IfcRelationsIndexer"; export * from "./IfcPropertiesManager"; export * from "./Utils";