From f6734a9a01f350812ed85314787f1157215c75a3 Mon Sep 17 00:00:00 2001 From: Felipe Moreira Date: Thu, 10 Apr 2025 15:32:36 +0200 Subject: [PATCH 01/15] Initial files for Complete QTO component --- src/bim-components/CompleteQTO/index.ts | 0 src/bim-components/CompleteQTO/src/CompleteQTO.ts | 0 src/bim-components/CompleteQTO/src/Template.ts | 0 src/bim-components/CompleteQTO/src/index.ts | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/bim-components/CompleteQTO/index.ts create mode 100644 src/bim-components/CompleteQTO/src/CompleteQTO.ts create mode 100644 src/bim-components/CompleteQTO/src/Template.ts create mode 100644 src/bim-components/CompleteQTO/src/index.ts diff --git a/src/bim-components/CompleteQTO/index.ts b/src/bim-components/CompleteQTO/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/bim-components/CompleteQTO/src/CompleteQTO.ts b/src/bim-components/CompleteQTO/src/CompleteQTO.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/bim-components/CompleteQTO/src/Template.ts b/src/bim-components/CompleteQTO/src/Template.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/bim-components/CompleteQTO/src/index.ts b/src/bim-components/CompleteQTO/src/index.ts new file mode 100644 index 0000000..e69de29 From bd101a1f727af2c02098f3414b76893d7e374788 Mon Sep 17 00:00:00 2001 From: Felipe Moreira Date: Thu, 10 Apr 2025 16:22:49 +0200 Subject: [PATCH 02/15] Create component architecture --- src/bim-components/CompleteQTO/index.ts | 1 + src/bim-components/CompleteQTO/src/CompleteQTO.ts | 14 ++++++++++++++ src/bim-components/CompleteQTO/src/Template.ts | 6 ++++++ src/bim-components/CompleteQTO/src/index.ts | 2 ++ src/bim-components/SimpleQTO/src/Template.ts | 2 +- src/components/Panels/{QTO.ts => SimpleQTO.ts} | 4 ++-- src/main.ts | 10 ++++++++-- 7 files changed, 34 insertions(+), 5 deletions(-) rename src/components/Panels/{QTO.ts => SimpleQTO.ts} (91%) diff --git a/src/bim-components/CompleteQTO/index.ts b/src/bim-components/CompleteQTO/index.ts index e69de29..3bd16e1 100644 --- a/src/bim-components/CompleteQTO/index.ts +++ b/src/bim-components/CompleteQTO/index.ts @@ -0,0 +1 @@ +export * from "./src"; diff --git a/src/bim-components/CompleteQTO/src/CompleteQTO.ts b/src/bim-components/CompleteQTO/src/CompleteQTO.ts index e69de29..633f27e 100644 --- a/src/bim-components/CompleteQTO/src/CompleteQTO.ts +++ b/src/bim-components/CompleteQTO/src/CompleteQTO.ts @@ -0,0 +1,14 @@ +import * as OBC from "@thatopen/components"; + +export class CompleteQTO extends OBC.Component implements OBC.Disposable { + static uuid = "663bebd3-ed4b-49fb-81ec-2be7c31ce2c2"; + enabled = true; + onDisposed: OBC.Event = new OBC.Event(); + + constructor(components: OBC.Components) { + super(components); + this.components.add(CompleteQTO.uuid, this); + } + + dispose() {} +} diff --git a/src/bim-components/CompleteQTO/src/Template.ts b/src/bim-components/CompleteQTO/src/Template.ts index e69de29..bbaa48e 100644 --- a/src/bim-components/CompleteQTO/src/Template.ts +++ b/src/bim-components/CompleteQTO/src/Template.ts @@ -0,0 +1,6 @@ +import * as OBC from "@thatopen/components"; +import * as BUI from "@thatopen/ui"; + +export const completeQTOTable = (components: OBC.Components) => {}; + +export const completeQTOWindow = (components: OBC.Components) => {}; diff --git a/src/bim-components/CompleteQTO/src/index.ts b/src/bim-components/CompleteQTO/src/index.ts index e69de29..6817970 100644 --- a/src/bim-components/CompleteQTO/src/index.ts +++ b/src/bim-components/CompleteQTO/src/index.ts @@ -0,0 +1,2 @@ +export * from "./CompleteQTO"; +export * from "./Template"; diff --git a/src/bim-components/SimpleQTO/src/Template.ts b/src/bim-components/SimpleQTO/src/Template.ts index 667d860..865897f 100644 --- a/src/bim-components/SimpleQTO/src/Template.ts +++ b/src/bim-components/SimpleQTO/src/Template.ts @@ -6,7 +6,7 @@ export interface QTOUIState { components: OBC.Components; } -export const qtoTool = (state: QTOUIState) => { +export const simpleQTOTool = (state: QTOUIState) => { const { components } = state; const simpleQto = components.get(SimpleQTO); diff --git a/src/components/Panels/QTO.ts b/src/components/Panels/SimpleQTO.ts similarity index 91% rename from src/components/Panels/QTO.ts rename to src/components/Panels/SimpleQTO.ts index 9b262d7..a97c1f1 100644 --- a/src/components/Panels/QTO.ts +++ b/src/components/Panels/SimpleQTO.ts @@ -1,11 +1,11 @@ import * as OBC from "@thatopen/components"; import * as BUI from "@thatopen/ui"; import * as OBF from "@thatopen/components-front"; -import { qtoTool } from "../../bim-components/SimpleQTO/src/Template"; +import { simpleQTOTool } from "../../bim-components/SimpleQTO/src/Template"; export default (components: OBC.Components) => { const highlighter = components.get(OBF.Highlighter); - const qtoTable = qtoTool({ components }); + const qtoTable = simpleQTOTool({ components }); highlighter.events.select.onHighlight.add(() => { qtoTable.expanded = true; diff --git a/src/main.ts b/src/main.ts index c5acc57..8728181 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,7 +12,7 @@ import { AppManager } from "./bim-components"; import { SimpleQTO } from "./bim-components/SimpleQTO/src/SimpleQTO"; import "./style.css"; -import QTO from "./components/Panels/QTO"; +import QTO from "./components/Panels/SimpleQTO"; import { customRelTree } from "./components/Panels/CustomRelTree"; import { CustomTree } from "./bim-components/CustomTree"; @@ -191,7 +191,7 @@ import { CustomTree } from "./bim-components/CustomTree"; + + > ${camera(world)} ${selection(components, world)} From 9949cafaa4a5765b01caa64b6ddc498d29cdde2c Mon Sep 17 00:00:00 2001 From: Felipe Moreira <118832082+Felipemore96@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:17:04 +0200 Subject: [PATCH 15/15] Refactor CompleteQTO and add ElementsTable panel for enhanced data display --- .../CompleteQTO/src/CompleteQTO.ts | 4 +- .../CompleteQTO/src/Template.ts | 299 +++++++++++++++++- src/components/Panels/ElementsTable.ts | 22 ++ src/main.ts | 5 +- 4 files changed, 321 insertions(+), 9 deletions(-) create mode 100644 src/components/Panels/ElementsTable.ts diff --git a/src/bim-components/CompleteQTO/src/CompleteQTO.ts b/src/bim-components/CompleteQTO/src/CompleteQTO.ts index cdbcc4c..81eba0e 100644 --- a/src/bim-components/CompleteQTO/src/CompleteQTO.ts +++ b/src/bim-components/CompleteQTO/src/CompleteQTO.ts @@ -41,8 +41,8 @@ export class CompleteQTO extends OBC.Component implements OBC.Disposable { private _categories: string[] = []; categoriesTable: BUI.Table | undefined; fullQTOTable: BUI.Table | undefined; - // private mainCategory = "Category"; - private mainCategory = "Name"; + private mainCategory = "Category"; + // private mainCategory = "Name"; get categories(): string[] { return this._categories; diff --git a/src/bim-components/CompleteQTO/src/Template.ts b/src/bim-components/CompleteQTO/src/Template.ts index 98134a1..4c06a7c 100644 --- a/src/bim-components/CompleteQTO/src/Template.ts +++ b/src/bim-components/CompleteQTO/src/Template.ts @@ -1,6 +1,7 @@ import * as OBC from "@thatopen/components"; import * as BUI from "@thatopen/ui"; import { CompleteQTO } from "./CompleteQTO"; +import tableData from "../../../assets/ifc_detailed_objects 1.json"; export interface CompleteQTOUIState { components: OBC.Components; @@ -10,12 +11,25 @@ export const categoriesTable = (state: CompleteQTOUIState) => { const { components } = state; const completeQto = components.get(CompleteQTO); + const categories: BUI.TableGroupData[] = [ + { data: { category: "Site" } }, + { data: { category: "Levels" } }, + { data: { category: "Project Information" } }, + { data: { category: "Mechanical Equipment" } }, + { data: { category: "Duct Fittings" } }, + { data: { category: "Duct Accessories" } }, + { data: { category: "Air Terminals" } }, + { data: { category: "Ducts" } }, + { data: { category: "Generic Models" } }, + ]; + const categoriesTable = BUI.Component.create(() => { return BUI.html` `; }); + categoriesTable.data = categories; categoriesTable.headersHidden = true; categoriesTable.addEventListener("rowcreated", (e) => { const { row } = e.detail; @@ -41,7 +55,7 @@ export const categoriesTable = (state: CompleteQTOUIState) => { }; }); - completeQto.categoriesTable = categoriesTable; + // completeQto.categoriesTable = categoriesTable; completeQto.onDisposed.add(() => { categoriesTable.remove(); @@ -54,17 +68,290 @@ export const QTOTable = (state: CompleteQTOUIState) => { const { components } = state; const completeQto = components.get(CompleteQTO); + // Define interfaces for your data structure + interface ElementData { + class: string; + name: string; + globalId: string; + psets: { + Pset_ProductRequirements?: { + Category: string; + }; + Pset_QuantityTakeOff?: { + Reference: string; + }; + ASML?: { + "Q Gewicht"?: number; + "3.4 Entiteit"?: string; + "3.5 Naamgeving element"?: string; + [key: string]: any; + }; + [key: string]: any; + }; + } + + // Define the table row types + type TableRowData = Record; + type QTORowData = { + id: string; + class: string; + name: string; + globalId: string; + category: string; + entityType?: string; + elementName?: string; + reference: string; + quantity?: number; + unit?: string; + floor?: string; + height?: number; + manufacturer?: string; + }; + + // Create the table component const fullQTOTable = BUI.Component.create(() => { return BUI.html` - + +
+ No data to display! +
+
`; }); - completeQto.fullQTOTable = fullQTOTable; + // Transform JSON data to table format + const transformDataToTable = (jsonData: Record) => { + const tableData: BUI.TableGroupData[] = []; - completeQto.onDisposed.add(() => { - fullQTOTable.remove(); + for (const [id, elementData] of Object.entries(jsonData)) { + const category = + elementData.psets?.Pset_ProductRequirements?.Category || "Unknown"; + const reference = + elementData.psets?.Pset_QuantityTakeOff?.Reference || ""; + const asmlData = elementData.psets?.ASML || {}; + const sparingenData = elementData.psets?.["ASML sparingen"] || {}; + + const rowData: QTORowData = { + id, + class: elementData.class, + name: elementData.name, + globalId: elementData.globalId, + category, + entityType: asmlData["3.4 Entiteit"], + elementName: asmlData["3.5 Naamgeving element"], + reference, + quantity: asmlData["Q Gewicht"], + unit: asmlData["Q Gewicht"] ? "kg" : undefined, + floor: sparingenData["SP.01 Verdieping"], + height: sparingenData["SP.13 Sparing hoogte hart t.o.v. verdieping "], + manufacturer: + elementData.psets?.Pset_ManufacturerTypeInformation?.Manufacturer, + }; + + tableData.push({ + data: rowData as unknown as TableRowData, + }); + } + + return tableData; + }; + + // Set the transformed data to the table + fullQTOTable.data = transformDataToTable(tableData); + + // Configure columns with custom widths and ordering + fullQTOTable.columns = [ + { name: "id", width: "80px" }, + { name: "class", width: "120px" }, + { name: "name", width: "200px" }, + { name: "category", width: "150px" }, + { name: "entityType", width: "150px" }, + { name: "elementName", width: "150px" }, + { name: "quantity", width: "80px" }, + { name: "unit", width: "60px" }, + { name: "floor", width: "200px" }, + { name: "height", width: "100px" }, + { name: "manufacturer", width: "150px" }, + ]; + + fullQTOTable.hiddenColumns = [ + "entityType", + "elementName", + "quantity", + "unit", + "floor", + "height", + "manufacturer", + "globalId", + ]; + + // Add data transformations for better display + fullQTOTable.dataTransform = { + quantity: (value: unknown) => (value ? `${value}` : "-"), + height: (value: unknown) => { + const numValue = + typeof value === "number" ? value : parseFloat(String(value)); + return Number.isNaN(numValue) ? "-" : `${numValue.toFixed(2)} mm`; + }, + class: (value: unknown) => { + const strValue = String(value); + const colorMap: Record = { + IfcBuildingElementProxy: "#cc0000", + }; + const color = colorMap[strValue] || "#9E9E9E"; + return BUI.html` + ${strValue} + `; + }, + }; + + // Add search functionality + const searchBox = BUI.Component.create(() => { + const onInput = (e: Event) => { + const input = e.target; + if (!(input instanceof BUI.TextInput)) return; + fullQTOTable.queryString = input.value; + }; + + return BUI.html` + + `; }); - return fullQTOTable; + // Add column visibility toggle + const addColumnVisibilityControls = () => { + const columnToggleBtn = BUI.Component.create(() => { + return BUI.html` + + `; + }); + + const columnMenu = BUI.Component.create(() => { + return BUI.html``; + }); + + // Create menu items for each column + const allColumns = [ + { name: "id", label: "ID" }, + { name: "class", label: "Class" }, + { name: "name", label: "Name" }, + { name: "category", label: "Category" }, + { name: "entityType", label: "Entity Type" }, + { name: "elementName", label: "Element Name" }, + { name: "quantity", label: "Quantity" }, + { name: "unit", label: "Unit" }, + { name: "floor", label: "Floor" }, + { name: "height", label: "Height" }, + { name: "manufacturer", label: "Manufacturer" }, + { name: "globalId", label: "Global ID" }, + ]; + + allColumns.forEach((column) => { + const isHidden = fullQTOTable.hiddenColumns.includes(column.name); + const menuItem = BUI.Component.create(() => { + const onChange = (e: Event) => { + const checkbox = e.target as HTMLInputElement; + if (checkbox.checked) { + fullQTOTable.hiddenColumns = fullQTOTable.hiddenColumns.filter( + (col) => col !== column.name, + ); + } else { + fullQTOTable.hiddenColumns = [ + ...fullQTOTable.hiddenColumns, + column.name, + ]; + } + }; + + return BUI.html` + +
+ + ${column.label} +
+
+ `; + }); + columnMenu.appendChild(menuItem); + }); + + columnToggleBtn.addEventListener("click", () => { + columnMenu.setAttribute( + "open", + columnMenu.hasAttribute("open") ? "" : "true", + ); + }); + + return BUI.html` +
+ ${columnToggleBtn} + ${columnMenu} +
+ `; + }; + + // Add export functionality + const addExportControls = () => { + const formatDropdown = BUI.Component.create( + () => BUI.html` + + + + + + `, + ); + + const fileNameInput = BUI.Component.create( + () => + BUI.html``, + ); + + const downloadBtn = BUI.Component.create(() => { + const onClick = () => { + const format = formatDropdown.value[0] as "csv" | "tsv" | "json"; + if (!format) return; + const fileName = fileNameInput.value.trim() || undefined; + fullQTOTable.downloadData(fileName, format); + }; + + return BUI.html` + + `; + }); + + return BUI.html` +
+ ${fileNameInput} + ${formatDropdown} + ${downloadBtn} +
+ `; + }; + + completeQto.fullQTOTable = fullQTOTable; + completeQto.onDisposed.add(() => fullQTOTable.remove()); + + // Return the complete table with controls + return BUI.Component.create(() => { + return BUI.html` +
+
+ ${searchBox} + ${addColumnVisibilityControls()} + ${addExportControls()} +
+ ${fullQTOTable} +
+ `; + }); }; diff --git a/src/components/Panels/ElementsTable.ts b/src/components/Panels/ElementsTable.ts new file mode 100644 index 0000000..402c940 --- /dev/null +++ b/src/components/Panels/ElementsTable.ts @@ -0,0 +1,22 @@ +import * as OBC from "@thatopen/components"; +import * as BUI from "@thatopen/ui"; +import { QTOTable } from "../../bim-components/CompleteQTO/src/Template"; + +export default (components: OBC.Components) => { + const elementsQTOTable = QTOTable({ components }); + + return BUI.Component.create(() => { + return BUI.html` + + + ${elementsQTOTable} + + + `; + }); +}; diff --git a/src/main.ts b/src/main.ts index 541a303..c1c9fe6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,7 @@ import settings from "./components/Panels/Settings"; import simpleQtoPanel from "./components/Panels/SimpleQTO"; import customRelTree from "./components/Panels/CustomRelTree"; import CompleteQTOPanel from "./components/Panels/CompleteQTO"; +import elementsTable from "./components/Panels/ElementsTable"; import load from "./components/Toolbars/Sections/Import"; import camera from "./components/Toolbars/Sections/Camera"; @@ -136,6 +137,7 @@ import { CompleteQTO } from "./bim-components/CompleteQTO"; const qtoPanel = simpleQtoPanel(components); const completeQTOPanel = CompleteQTOPanel(components); const customTreePanel = customRelTree(components); + const tablePanel = elementsTable(components); const leftPanel = BUI.Component.create(() => { return BUI.html` @@ -264,10 +266,11 @@ import { CompleteQTO } from "./bim-components/CompleteQTO"; main: { template: ` "empty" 1fr + "tablePanel" 1fr "toolbar" auto /1fr `, - elements: { toolbar }, + elements: { toolbar, tablePanel }, }, second: { template: `