From e3ac35e6c53e98153b0960fc2789e4e1a0326df0 Mon Sep 17 00:00:00 2001 From: Curiosit Date: Fri, 29 Mar 2024 20:10:11 +0100 Subject: [PATCH] fixed total carbon footprint calculation --- src/bim-components/CarbonTool/index.ts | 157 ++++++- .../CarbonTool/src/ElementCard.ts | 18 +- .../CarbonTool/src/ResultsCard.ts | 13 +- src/react-components/ComponentPage.tsx | 430 ++++++++++++++++++ style.css | 10 + 5 files changed, 605 insertions(+), 23 deletions(-) create mode 100644 src/react-components/ComponentPage.tsx diff --git a/src/bim-components/CarbonTool/index.ts b/src/bim-components/CarbonTool/index.ts index 5eac8b5..79a3365 100644 --- a/src/bim-components/CarbonTool/index.ts +++ b/src/bim-components/CarbonTool/index.ts @@ -73,6 +73,10 @@ export class CarbonTool extends OBC.Component implement components.scene this._qtoResultByElementName = {} this._qtoResult = {} + + this.callback = this.callback.bind(this); + this.sumGWP = this.sumGWP.bind(this); + this.setUI() //this.getQuantities() @@ -155,6 +159,9 @@ export class CarbonTool extends OBC.Component implement carbonWindowBtn.active = !carbonWindowBtn.active carbonWindow.visible = carbonWindowBtn.active + if(carbonWindow.visible) { + + } console.log(carbonWindowBtn.active) console.log(carbonWindow.visible) @@ -201,6 +208,51 @@ export class CarbonTool extends OBC.Component implement }) */ + } + callback() { + console.log("Calling back from card") + this.sumGWP() + + } + + sumGWP() { + // Initialize the total sum of GWP (Global Warming Potential) + let totalGWP = 0; + + // Iterate through each ElementCard in the elementCardList + this.elementCardList.forEach((elementCard) => { + // Check if the elementData and 'CF values' exist to avoid runtime errors + if (elementCard.elementData && elementCard.elementData['CF values']) { + // Access the 'Carbon Footprint' value and add it to the totalGWP + const carbonFootprint = elementCard.elementData['CF values']['Carbon Footprint']; + if (typeof carbonFootprint === 'number') { + totalGWP += carbonFootprint; + } + } + }); + + // Optionally, you can log or perform further actions with the totalGWP + console.log(`Total Carbon Footprint: ${totalGWP} kg CO2eq`); + + // Return the total GWP if needed + + + + + const resultsWindow = this.uiElement.get("carbonWindow") + if(this.resultsCard) { + this.resultsCard.removeFromParent() + } + const resultsCard = new ResultsCard(this.components) + this.resultsCard = resultsCard + + resultsCard.totalResult = totalGWP + + resultsWindow.addChild(resultsCard) + + + + return totalGWP; } async updateUI () { const qtoList = this.uiElement.get("qtoWindow") @@ -210,7 +262,8 @@ export class CarbonTool extends OBC.Component implement for (const elementName in this._qtoResultByElementName) { console.log(this._qtoResultByElementName[elementName]) - const elementCard = new ElementCard(this.components, this.epdxData, this.constructionComponents); + const elementCard = new ElementCard(this.components, this.epdxData, this.constructionComponents, this.callback); + //elementCard.callback = this.callback() elementCard.data = this._qtoResultByElementName[elementName] elementCard.elementName = elementName console.log(this.materialForm) @@ -244,6 +297,8 @@ export class CarbonTool extends OBC.Component implement console.log(elementData) console.log("UPDATING RESULTS") resultsCard.resultData=elementData + console.log(elementData) + console.log(resultsCard.totalResult) resultsWindow.addChild(resultsCard) } resetWindow() { @@ -364,7 +419,7 @@ export class CarbonTool extends OBC.Component implement idMap = [] //console.log("________________________________________________________________________") //console.log(elementID) - //console.log(elements[elementID]) + console.log(elements[elementID]) const name = properties[elements[elementID].expressID].Name.value let nameValue @@ -383,7 +438,7 @@ export class CarbonTool extends OBC.Component implement //console.log(qtoResultByElementName[nameWithID]) //console.log("________________________________________________________________________") - //console.log(resultRow) + console.log(resultRow) OBC.IfcPropertiesUtils.getRelationMap( properties, WEBIFC.IFCRELDEFINESBYPROPERTIES, @@ -391,16 +446,82 @@ export class CarbonTool extends OBC.Component implement //console.log("GET RELATION MAP") const set = properties[setID] - if ( set.type !== WEBIFC.IFCELEMENTQUANTITY) { return} + //console.log(setID) + //console.log(set) + //console.log(relatedIDs) + /* if ( set.type !== WEBIFC.IFCELEMENTQUANTITY) { return} */ + + // PROPERTYSETSINGLEVALUE DIMENSIONS (PANELS) + if(elementType == 'curtainPanels') { + + console.log("PANELS") + if ( set.type == WEBIFC.IFCPROPERTYSET) { + console.log(set) + const expressIDs = idMap + const workingIDs = expressIDs.filter(id => relatedIDs.includes(id)); + if (workingIDs.length > 0) { + console.log('Working IDs:', workingIDs); + } else { + console.log('No common IDs found between expressIDs and relatedIDs'); + } + const { name: setName} = OBC.IfcPropertiesUtils.getEntityName(properties, setID) + console.log(setName) + if ( !setName || workingIDs.length === 0 || setName != 'Dimensions') { return} + + if (!(setName in resultRow)) { + resultRow[setName] = {} + } + // setName = dimensions + console.log(workingIDs[0]) + OBC.IfcPropertiesUtils.getPsetProps(properties, workingIDs[0], (foundID) => { + console.log(`Property found with expressID: ${foundID}`); + }) + + OBC.IfcPropertiesUtils.getQsetQuantities( + properties, + setID, + (qtoID) => { + console.log(properties[qtoID]) + const { name: qtoName} = OBC.IfcPropertiesUtils.getEntityName(properties, qtoID) + console.log(qtoName) + const { value } = OBC.IfcPropertiesUtils.getQuantityValue(properties, qtoID) + console.log(value) + if(!qtoName || !value) {return} + console.log(qtoName) + if (!(qtoName in resultRow[setName])) { + resultRow[setName][qtoName] = value + + } + if ((qtoName in resultRow[setName])) { + resultRow[setName][qtoName] = Math.min(resultRow[setName][qtoName], value) //find a lower value, as there are multiple netVolumes !!!!!! + + } + //resultRow[setName][qtoName] = value + + + console.log(resultRow) + } + ) + + } + console.log(resultRow) + } + + + + // BASE QUANTITIES + if ( set.type !== WEBIFC.IFCELEMENTQUANTITY) { return } const expressIDs = idMap const workingIDs = expressIDs.filter(id => relatedIDs.includes(id)); + //console.log(workingIDs) + if (workingIDs.length > 0) { - //console.log('Working IDs:', workingIDs); + console.log('Working IDs:', workingIDs); } else { - //console.log('No common IDs found between expressIDs and relatedIDs'); + console.log('No common IDs found between expressIDs and relatedIDs'); } const { name: setName} = OBC.IfcPropertiesUtils.getEntityName(properties, setID) @@ -416,9 +537,9 @@ export class CarbonTool extends OBC.Component implement properties, setID, (qtoID) => { - //console.log(properties[qtoID]) + console.log(properties[qtoID]) const { name: qtoName} = OBC.IfcPropertiesUtils.getEntityName(properties, qtoID) - //console.log(qtoName) + console.log(qtoName) const { value } = OBC.IfcPropertiesUtils.getQuantityValue(properties, qtoID) //console.log(value) if(!qtoName || !value) {return} @@ -434,13 +555,13 @@ export class CarbonTool extends OBC.Component implement //resultRow[setName][qtoName] = value } ) - console.log(nameValue) - console.log(qtoResultByElementFamily) + //console.log(nameValue) + //console.log(qtoResultByElementFamily) if (!(nameValue in qtoResultByElementFamily)) { qtoResultByElementFamily[nameValue] = {}; // Initialize if not exists } - console.log(qtoResultByElementFamily[nameValue]) + //console.log(qtoResultByElementFamily[nameValue]) if (!("CF values" in qtoResultByElementFamily[nameValue])) { qtoResultByElementFamily[nameValue]["CF values"] = { @@ -460,6 +581,7 @@ export class CarbonTool extends OBC.Component implement console.log(qtoResultByElementName[nameValue]) console.log(elementType) + console.log(resultRow) console.log(resultRow["BaseQuantities"]) let area if(elementType == "windows" || elementType == "doors") { @@ -540,7 +662,16 @@ export class CarbonTool extends OBC.Component implement properties, WEBIFC.IFCWINDOW ) - + const curtainWalls = OBC.IfcPropertiesUtils.getAllItemsOfType( + properties, + WEBIFC.IFCCURTAINWALL + ) + console.log(curtainWalls) + + const curtainPanels = OBC.IfcPropertiesUtils.getAllItemsOfType( + properties, + WEBIFC.IFCPLATE + ) const elements = typeList @@ -555,6 +686,8 @@ export class CarbonTool extends OBC.Component implement console.log(doorResults) const windowResults = this.calculateQuantities(properties, windows, "windows") console.log(windowResults) + const curtainWallsRes = this.calculateQuantities(properties, curtainPanels, "curtainPanels") + console.log(curtainWallsRes) this._qtoResultByElementName = { ...wallResults, ...slabResults , ...windowResults, ...doorResults}; diff --git a/src/bim-components/CarbonTool/src/ElementCard.ts b/src/bim-components/CarbonTool/src/ElementCard.ts index 277dc11..f200827 100644 --- a/src/bim-components/CarbonTool/src/ElementCard.ts +++ b/src/bim-components/CarbonTool/src/ElementCard.ts @@ -8,6 +8,7 @@ import { Component } from "../../../classes/Component" export class ElementCard extends OBC.SimpleUIComponent { + callback: VoidFunction onDelete = new OBC.Event() onCardClick = new OBC.Event() //elementData @@ -74,10 +75,12 @@ export class ElementCard extends OBC.SimpleUIComponent { } } - + setCallback(callbackFunction) { + this.callback = callbackFunction; + } - constructor(components: OBC.Components, epdxData, constructionComponents) { + constructor(components: OBC.Components, epdxData, constructionComponents, callback) { const template = ` @@ -93,9 +96,9 @@ export class ElementCard extends OBC.SimpleUIComponent {
-

- -

+
+ ...select a component +
@@ -124,7 +127,7 @@ export class ElementCard extends OBC.SimpleUIComponent { }) this.setSlot("actionButtons", new OBC.SimpleUIComponent(this._components)) - + this.callback = callback } async dispose () { @@ -181,6 +184,9 @@ export class ElementCard extends OBC.SimpleUIComponent { this.elementData = this.elementSet //this.calculateGWP(this.elementSet) + + + this.callback() }); diff --git a/src/bim-components/CarbonTool/src/ResultsCard.ts b/src/bim-components/CarbonTool/src/ResultsCard.ts index f7623b1..d97cd0a 100644 --- a/src/bim-components/CarbonTool/src/ResultsCard.ts +++ b/src/bim-components/CarbonTool/src/ResultsCard.ts @@ -21,6 +21,7 @@ export class ResultsCard extends OBC.SimpleUIComponent { return this.resultDataset } set resultData(object) { + console.log("Set result") this.calculateGWP(object) } set totalResult(value) { @@ -54,6 +55,7 @@ export class ResultsCard extends OBC.SimpleUIComponent {
+
@@ -64,7 +66,6 @@ export class ResultsCard extends OBC.SimpleUIComponent { this.resultContainer = this.getInnerElement("resultsContainer") as HTMLParagraphElement - @@ -72,6 +73,8 @@ export class ResultsCard extends OBC.SimpleUIComponent { } + + async dispose () { this.dispose() @@ -85,16 +88,16 @@ export class ResultsCard extends OBC.SimpleUIComponent { } calculateGWP(list) { - //console.log("Calculating GWP") + console.log("Calculating GWP") let tempResult = 0 for (const item in list) { const sets = list[item] for(const setName in sets) { - //console.log(setName) + console.log(setName) if(setName == "CF values") { const element = sets[setName] - //console.log(element) - //console.log(element["Carbon Footprint"]) + console.log(element) + console.log(element["Carbon Footprint"]) const result = element["Carbon Footprint"] tempResult += result diff --git a/src/react-components/ComponentPage.tsx b/src/react-components/ComponentPage.tsx new file mode 100644 index 0000000..7adf77a --- /dev/null +++ b/src/react-components/ComponentPage.tsx @@ -0,0 +1,430 @@ +import * as React from "react" +import * as Router from "react-router-dom" + +import { formatDate, roundNumber, setupModal, showModal } from "../utils/utils" + +import { deleteDocument, getCollection } from "../firebase" + +import { v4 as uuidv4 } from 'uuid' +import { Component, IComponent } from "../classes/Component" +import * as Firestore from "firebase/firestore" +import { calculateTotalEPDGWP, convertToEpdx } from "../utils/epdx" + +import { EPD } from "epdx" + +import { SearchBox } from "./SearchBox"; +import { getFirestoreComponents, getFirestoreMaterials } from "../utils/materialdata" +import { ComponentCard } from "./ComponentCard" + +import { ComponentSubtype } from "../classes/Component" +import { AskAI } from "./AskAI" +const componentsCollection = getCollection("/components") +interface Props { + +} + +export function ComponentsPage(props: Props) { + + const [selectedLayers, setSelectedLayers] = React.useState([]); + const [showQuestion, setShowQuestion] = React.useState(false); + const [initialized, setInitialized] = React.useState(false); + const [epdxData, setEpdxData] = React.useState([]); + + const [componentData, setComponentData] = React.useState([]); + + const [showComponentData, setShowComponentData] = React.useState([]); + const [newComponentLayers, setNewComponentLayers] = React.useState(1); + + let num = 0 + + const [searchQuery, setSearchQuery] = React.useState(""); + + const onComponentSearch = (value: string) => { + setSearchQuery(value); + }; + + const filteredComponents = showComponentData.filter(component => + component.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const componentCards = filteredComponents.map((component) => { + + num += 1 + console.log(component) + return + + + + }) + + + + React.useEffect(() => { + const getData = async () => { + const data = await getFirestoreMaterials() + const epdData = convertToEpdx(data) + + const compData = await getFirestoreComponents() + + console.log(compData) + setEpdxData(epdData) + + setComponentData(compData) + setShowComponentData(compData) + if (data) { + setInitialized(true) + } + } + if (!initialized) { + getData() + } + else { + //console.log(materialData) + } + return () => { + } + }, [initialized]) + + const onNewComponentClick = () => { + const modal = document.getElementById("new-component-modal"); + if (modal && modal instanceof HTMLDialogElement) { + setNewComponentLayers(1) + + const simulatedEvent = { + preventDefault: () => { }, + }; + + + onAddLayerClick(simulatedEvent); + modal.showModal(); + + } + else { + console.warn("The provided modal wasn't found. "); + } + } + const onFormSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + const form = e.target as HTMLFormElement; + const nameInput = form.querySelector('input[name="name"]') as HTMLInputElement; + const name = nameInput.value.trim(); + + if (!name) { + alert('Please provide a name for the component.'); + return; + } + + console.log("Name:", name); + + const subtypeInput = form.querySelector('select[name="subtype"]') as HTMLSelectElement; + const subtype = subtypeInput.value; + + const layerInputs = form.querySelectorAll('input[name^="layer-"], select[name^="layer-"]'); + const layerValues = []; + const amountValues = []; + + let isValid = true; + + layerInputs.forEach(layerInput => { + let value; + if (layerInput.tagName === 'INPUT') { + value = layerInput.value; + } else if (layerInput.tagName === 'SELECT') { + const selectedIndex = layerInput.selectedIndex; + value = layerInput.options[selectedIndex].value; + } + layerValues.push(value); + + const layerNumber = layerInput.getAttribute('name').split('-')[1]; + const amountInput = form.querySelector(`input[name="amount-${layerNumber}"]`) as HTMLInputElement; + const amount = amountInput ? parseFloat(amountInput.value.trim()) : NaN; + + if (!value || isNaN(amount)) { + isValid = false; + return; + } + amountValues.push(amount); + }); + + if (!isValid) { + alert('Please select a value and specify a valid amount for each layer.'); + return; + } + + console.log("Layer values:", layerValues); + console.log("Amount values:", amountValues); + + const layersData = layerValues.map((layerValue, index) => ({ + value: layerValue, + amount: amountValues[index] + })); + + const layersJSON = JSON.stringify(layersData); + console.log(layersJSON); + + const component = new Component( + name as string, + uuidv4(), + layersJSON, + subtype as ComponentSubtype + ); + + const componentData = component.toPlainObject(); + try { + if (true) { + const result = await Firestore.addDoc(componentsCollection, componentData); + if (result) { + const newData = await getFirestoreComponents(); + setComponentData(newData); + setShowComponentData(newData); + } + form.reset(); + const modal = document.getElementById("new-component-modal"); + if (modal && modal instanceof HTMLDialogElement) { + modal.close(); + } else { + console.warn("The provided modal wasn't found."); + } + } + } + catch (err) { + alert(err); + } + }; + + + function extractLayerValues(formData: any): { [key: string]: any } { + const layerValues: { [key: string]: any } = {}; + for (const key in formData) { + if (formData.hasOwnProperty(key) && /^layer\d{2}$/.test(key)) { + + layerValues[key] = formData[key]; + } + } + return layerValues; + } + function countLayers(formData: any): number { + let layerCount = 0; + for (const key in formData) { + if (formData.hasOwnProperty(key) && /^layer\d{2}$/.test(key)) { + + layerCount++; + } + } + return layerCount; + } + + const onAddLayerClick = (e) => { + e.preventDefault(); + const layerDiv = document.getElementById("component-layers"); + + if (layerDiv && layerDiv instanceof HTMLDivElement) { + const newLayerHTML = generateAnotherLayerDiv(newComponentLayers, epdxData); + + layerDiv.insertAdjacentHTML("beforeend", newLayerHTML); + const newLayerSelect = layerDiv.querySelector(`select[name="layer-${newComponentLayers.toString().padStart(2, '0')}"]`) as HTMLSelectElement; + if (newLayerSelect) { + newLayerSelect.addEventListener('change', handleDropdownChange); + } + setNewComponentLayers(prevLayers => prevLayers + 1); + } else { + console.warn("The provided div wasn't found."); + } + } + + function generateAnotherLayerDiv(layerNumber: number, epdxData: EPD[]): string { + let dropdownOptions = ''; + epdxData.forEach(entry => { + dropdownOptions += ``; + }); + + return ` +
+

+ +

+

+ + +

+
+ `; + } + + + function handleDropdownChange(event: Event) { + const select = event.target as HTMLSelectElement; + const selectedOption = select.options[select.selectedIndex]; + const unit = selectedOption.getAttribute('data-unit'); + const value = selectedOption.value; + console.log('Selected unit:', unit); + let newSelectedLayers = [...selectedLayers]; + newSelectedLayers[parseInt(select.name.split('-')[1]) - 1] = selectedOption.text; + setSelectedLayers(newSelectedLayers); + setShowQuestion(newSelectedLayers.length > 0); + const unitPlaceholder = select.closest('.form-field-container').querySelector('.unit-placeholder') as HTMLElement; + + if (unitPlaceholder) { + unitPlaceholder.textContent = unit || ''; + } else { + console.warn("Unit placeholder not found."); + } + } + + function updateUnit(selectElement) { + const selectedOption = selectElement.options[selectElement.selectedIndex]; + const unitPlaceholder = selectElement.parentNode.nextElementSibling.querySelector('.unit-placeholder'); + unitPlaceholder.textContent = selectedOption.getAttribute('data-unit'); + } + + function populateDropdown(dropdown) { + + dropdown.innerHTML = ''; + + epdxData.forEach(function (entry) { + var option = document.createElement("option"); + option.text = entry.name; + option.value = entry.id; + dropdown.add(option); + }); + } + + const onCancelClick = () => { + const modal = document.getElementById("new-component-modal"); + const layerDiv = document.getElementById("component-layers"); + + if (modal && modal instanceof HTMLDialogElement && layerDiv) { + setNewComponentLayers(1) + layerDiv.innerHTML = ''; // Clear layer HTML content + modal.close(); + } else { + console.warn("The provided modal or layer div wasn't found."); + } + }; + + const constructQuestionFromLayers = () => { + if (selectedLayers.length === 0) { + return "Please add layers to see the question."; + } + return `What can be improved in this material combination to lower carbon footprint?: ${selectedLayers.join(', ')}. Write opinion on each layer, and suggest replacement. Be short`; + }; + + + + return ( + +
+ +
onFormSubmit(e)} id="new-component-form" style={{ width: '700px' }}> +

New component

+
+
+ + +

+ TIP: Give it a short name +

+
+
+ + +
+
+ {/*
+ + + +
*/} +
+ +
+ + + + +
+
+
+
+
+
+

+ folder_copy Material Library +

+ onComponentSearch(value)} /> +
+ + +
+
+ + + { + componentData.length > 0 ? +
+ {componentCards} +
+ : +
+ No components found! +
+ + } +
+ + ) +} \ No newline at end of file diff --git a/style.css b/style.css index 8a55ac5..48fb896 100644 --- a/style.css +++ b/style.css @@ -505,6 +505,16 @@ text-align: center; outline: 1px solid #969696; } + +.element-component { + padding: 10px; + background-color: var(--background-200); + border-radius: 10px; + margin-bottom: 5px; + + +} + .qtyValueList { padding: 10px; background-color: var(--background-200);