FreeCAD / src /Mod /BIM /bimcommands /BimIfcQuantities.py
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2018 Yorik van Havre <yorik@uncreated.net> *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
"""This module contains FreeCAD commands for the BIM workbench"""
import csv
import os
import FreeCAD
import FreeCADGui
QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP
translate = FreeCAD.Qt.translate
PARAMS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM")
QPROPS = [
"Length",
"Width",
"Height",
"Area",
"HorizontalArea",
"VerticalArea",
"Volume",
]
TR_QPROPS = [
translate("BIM", "Length"),
translate("BIM", "Width"),
translate("BIM", "Height"),
translate("BIM", "Area"),
translate("BIM", "Horizontal Area"),
translate("BIM", "Vertical Area"),
translate("BIM", "Volume"),
]
QTO_TYPES = {
"IfcQuantityArea": "App::PropertyArea",
"IfcQuantityCount": "App::PropertyInteger",
"IfcQuantityLength": "App::PropertyLength",
"IfcQuantityNumber": "App::PropertyInteger",
"IfcQuantityTime": "App::PropertyTime",
"IfcQuantityVolume": "App::PropertyVolume",
"IfcQuantityWeight": "App::PropertyMass",
}
class BIM_IfcQuantities:
def GetResources(self):
return {
"Pixmap": "BIM_IfcQuantities",
"MenuText": QT_TRANSLATE_NOOP("BIM_IfcQuantities", "Manage IFC Quantities"),
"ToolTip": QT_TRANSLATE_NOOP(
"BIM_IfcQuantities",
"Manages how the quantities of different elements of the BIM project will be exported to IFC",
),
}
def IsActive(self):
v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
return v
def Activated(self):
# only raise the dialog if it is already open
if getattr(self, "form", None):
self.form.raise_()
return
from PySide import QtGui
# build objects list
self.objectslist = {}
self.ifcqtolist = {}
for obj in FreeCAD.ActiveDocument.Objects:
role = self.getRole(obj)
if role:
self.objectslist[obj.Name] = role
# support for arrays
array = self.getArray(obj)
for i in range(array):
if i > 0: # the first item already went above
self.objectslist[obj.Name + "+array" + str(i)] = role
try:
import ArchIFC
self.ifcroles = ArchIFC.IfcTypes
except (ImportError, AttributeError):
import ArchComponent
self.ifcroles = ArchComponent.IfcRoles
# load the form and set the tree model up
self.form = FreeCADGui.PySideUic.loadUi(":/ui/dialogIfcQuantities.ui")
self.form.setWindowIcon(QtGui.QIcon(":/icons/BIM_IfcQuantities.svg"))
w = PARAMS.GetInt("BimIfcQuantitiesDialogWidth", 680)
h = PARAMS.GetInt("BimIfcQuantitiesDialogHeight", 512)
self.form.resize(w, h)
self.get_qtos()
# quantities tab
self.qmodel = QtGui.QStandardItemModel()
self.form.quantities.setModel(self.qmodel)
self.form.quantities.setUniformRowHeights(True)
self.form.quantities.setItemDelegate(QtGui.QStyledItemDelegate())
self.qmodel.dataChanged.connect(self.setChecked)
self.form.buttonBox.accepted.connect(self.accept)
self.form.rejected.connect(self.reject) # also triggered by self.form.buttonBox.rejected
self.form.quantities.clicked.connect(self.onClickTree)
if hasattr(self.form.onlyVisible, "checkStateChanged"): # Qt version >= 6.7.0
self.form.onlyVisible.checkStateChanged.connect(self.update)
else: # Qt version < 6.7.0
self.form.onlyVisible.stateChanged.connect(self.update)
self.form.buttonRefresh.clicked.connect(self.update)
self.form.buttonApply.clicked.connect(self.add_qto)
# center the dialog over FreeCAD window
mw = FreeCADGui.getMainWindow()
self.form.move(
mw.frameGeometry().topLeft() + mw.rect().center() - self.form.rect().center()
)
self.update()
self.form.show()
def getArray(self, obj):
"returns a count number if this object needs to be duplicated"
import Draft
if len(obj.InList) == 1:
parent = obj.InList[0]
if Draft.getType(parent) == "Array":
return parent.Count
return 0
def decamelize(self, s):
return "".join([" " + c if c.isupper() else c for c in s]).strip(" ")
def get_qtos(self):
"populates the qtos combo box"
def read_csv(csvfile):
result = {}
if os.path.exists(csvfile):
with open(csvfile, "r") as f:
reader = csv.reader(f, delimiter=";")
for row in reader:
result[row[0]] = row[1:]
return result
self.qtodefs = {}
qtopath = os.path.join(
FreeCAD.getResourceDir(), "Mod", "BIM", "Presets", "qto_definitions.csv"
)
custompath = os.path.join(FreeCAD.getUserAppDataDir(), "BIM", "CustomQtos.csv")
self.qtodefs = read_csv(qtopath)
self.qtodefs.update(read_csv(custompath))
self.qtokeys = [
"".join(map(lambda x: x if x.islower() else " " + x, t[4:]))[1:]
for t in self.qtodefs.keys()
]
self.qtokeys.sort()
self.form.comboQto.addItems(
[
translate("BIM", "Add quantity set..."),
]
+ self.qtokeys
)
def add_qto(self):
"Adds a standard qto set to the todo list"
index = self.form.comboQto.currentIndex()
if index <= 0:
return
if len(FreeCADGui.Selection.getSelection()) != 1:
return
obj = FreeCADGui.Selection.getSelection()[0]
qto = list(self.qtodefs.keys())[index - 1]
self.ifcqtolist.setdefault(obj.Name, []).append(qto)
self.update_line(obj.Name, qto)
FreeCAD.Console.PrintMessage(translate("BIM", "Adding quantity set") + ": " + qto + "\n")
def apply_qto(self, obj, qto):
"Adds a standard qto set to the object"
val = self.qtodefs[qto]
qset = None
if hasattr(obj, "StepId"):
from nativeifc import ifc_tools
ifcfile = ifc_tools.get_ifcfile(obj)
element = ifc_tools.get_ifc_element(obj)
if not ifcfile or not element:
return
qset = ifc_tools.api_run("pset.add_qto", ifcfile, product=element, name=qto)
for i in range(0, len(val), 2):
qname = val[i]
qtype = QTO_TYPES[val[i + 1]]
if not qname in obj.PropertiesList:
obj.addProperty(qtype, qname, "Quantities", val[i + 1], locked=True)
qval = 0
i = self.get_row(obj.Name)
if i > -1:
for j, p in enumerate(QPROPS):
it = self.qmodel.item(i, j + 1)
t = it.text()
if t:
t = t.replace("²", "^2").replace("³", "^3")
qval = FreeCAD.Units.Quantity(t).Value
if qval:
setattr(obj, qname, qval)
if hasattr(obj, "StepId") and qset:
ifc_tools.api_run("pset.edit_qto", ifcfile, qto=qset, properties={qname: qval})
def update(self, index=None):
"""updates the tree widgets in all tabs. Index is not used,
it is just there to match a qt slot requirement"""
from PySide import QtCore, QtGui
import Draft
# quantities tab
self.qmodel.clear()
self.qmodel.setHorizontalHeaderLabels([translate("BIM", "Label")] + TR_QPROPS)
self.form.quantities.setColumnWidth(0, 200) # TODO remember width
quantheaders = self.form.quantities.header() # QHeaderView instance
quantheaders.setSectionsClickable(True)
quantheaders.sectionClicked.connect(self.quantHeaderClicked)
# sort by type
groups = {}
for name, role in self.objectslist.items():
groups.setdefault(role, []).append(name)
for names in groups.values():
suffix = ""
for name in names:
if "+array" in name:
name = name.split("+array")[0]
suffix = " (duplicate)"
obj = FreeCAD.ActiveDocument.getObject(name)
if obj:
if (not self.form.onlyVisible.isChecked()) or obj.ViewObject.isVisible():
if obj.isDerivedFrom("Part::Feature") and not (
Draft.getType(obj) == "Site"
):
it1 = QtGui.QStandardItem(obj.Label + suffix)
it1.setToolTip(name + suffix)
it1.setEditable(False)
it1.setIcon(obj.ViewObject.Icon)
props = []
for prop in QPROPS:
it = QtGui.QStandardItem()
val = None
if hasattr(obj, prop) and ("Hidden" not in obj.getEditorMode(prop)):
val = self.get_text(obj, prop)
it.setText(val)
it.setCheckable(True)
if val != None:
d = None
if hasattr(obj, "IfcAttributes"):
d = obj.IfcAttributes
elif hasattr(obj, "IfcData"):
d = obj.IfcData
if d:
if ("Export" + prop in d) and (
d["Export" + prop] == "True"
):
it.setCheckState(QtCore.Qt.Checked)
elif self.has_qto(obj, prop):
it.setCheckState(QtCore.Qt.Checked)
if val == 0:
it.setIcon(QtGui.QIcon(":/icons/warning.svg"))
self.set_editable(it, prop)
props.append(it)
self.qmodel.appendRow([it1] + props)
def has_qto(self, obj, prop):
"""Says if the given object has the given prop in a qto set"""
if not "StepId" in obj.PropertiesList:
return False
from nativeifc import ifc_tools
element = ifc_tools.get_ifc_element(obj)
if not element:
return False
for rel in getattr(element, "IsDefinedBy", []):
pset = rel.RelatingPropertyDefinition
if pset.is_a("IfcElementQuantity"):
if pset.Name in self.qtodefs:
if prop in self.qtodefs[pset.Name]:
return True
return False
def get_text(self, obj, prop):
"""Gets the text from a property"""
val = getattr(obj, prop, "0")
txt = val.getUserPreferred()[0].replace("^2", "²").replace("^3", "³")
return txt
def get_row(self, name):
"""Returns the row number corresponding to the given object name"""
for i in range(self.qmodel.rowCount()):
if self.qmodel.item(i).toolTip().split(" ")[0] == name:
return i
return -1
def update_line(self, name, qto):
"""Updates a single line of the table, without updating
the actual object"""
from PySide import QtCore
i = self.get_row(name)
if i == -1:
return
obj = FreeCAD.ActiveDocument.getObject(name)
qto_val = self.qtodefs[qto]
for j, p in enumerate(QPROPS):
it = self.qmodel.item(i, j + 1)
if p in obj.PropertiesList:
val = self.get_text(obj, p)
it.setText(val)
self.set_editable(it, p)
it.setCheckable(True)
elif p in qto_val:
it.setText("0")
it.setCheckable(True)
it.setCheckState(QtCore.Qt.Checked)
self.set_editable(it, p)
def set_editable(self, it, prop):
"""Checks if the given prop should be editable, and sets it"""
if prop in ["Area", "HorizontalArea", "VerticalArea", "Volume"]:
it.setEditable(False)
else:
it.setEditable(True)
def getRole(self, obj):
"""gets the IFC class of this object"""
if hasattr(obj, "IfcType"):
return obj.IfcType
elif hasattr(obj, "IfcRole"):
return obj.IfcRole
elif hasattr(obj, "IfcClass"):
return obj.IfcClass
else:
return None
def accept(self):
"""OK pressed"""
PARAMS.SetInt("BimIfcQuantitiesDialogWidth", self.form.width())
PARAMS.SetInt("BimIfcQuantitiesDialogHeight", self.form.height())
self.form.hide()
changed = False
if self.ifcqtolist:
if not changed:
FreeCAD.ActiveDocument.openTransaction("Change quantities")
changed = True
for key, val in self.ifcqtolist.items():
obj = FreeCAD.ActiveDocument.getObject(key)
if obj:
for qto in val:
self.apply_qto(obj, qto)
for row in range(self.qmodel.rowCount()):
name = self.qmodel.item(row, 0).toolTip()
obj = FreeCAD.ActiveDocument.getObject(name)
if obj:
for i in range(len(QPROPS)):
item = self.qmodel.item(row, i + 1)
val = item.text()
sav = bool(item.checkState())
if i < 3: # Length, Width, Height, value can be changed
if hasattr(obj, QPROPS[i]):
if getattr(obj, QPROPS[i]).getUserPreferred()[0] != val:
setattr(obj, QPROPS[i], val)
if not changed:
FreeCAD.ActiveDocument.openTransaction("Change quantities")
changed = True
d = None
if hasattr(obj, "IfcAttributes"):
d = obj.IfcAttributes
att = "IfcAttributes"
elif hasattr(obj, "IfcData"):
d = obj.IfcData
att = "IfcData"
if d:
if sav:
if (not "Export" + QPROPS[i] in d) or (
d["Export" + QPROPS[i]] == "False"
):
d["Export" + QPROPS[i]] = "True"
setattr(obj, att, d)
if not changed:
FreeCAD.ActiveDocument.openTransaction("Change quantities")
changed = True
else:
if "Export" + QPROPS[i] in d:
if d["Export" + QPROPS[i]] == "True":
d["Export" + QPROPS[i]] = "False"
setattr(obj, att, d)
if not changed:
FreeCAD.ActiveDocument.openTransaction("Change quantities")
changed = True
elif "StepId" not in obj.PropertiesList:
FreeCAD.Console.PrintError(
translate(
"BIM", "Cannot save quantities settings for object %1"
).replace("%1", obj.Label)
+ "\n"
)
if changed:
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
return self.reject()
def reject(self):
self.form.hide()
del self.form
return True
def setChecked(self, id1, id2):
sel = self.form.quantities.selectedIndexes()
state = self.qmodel.itemFromIndex(id1).checkState()
if len(sel) > 7:
for idx in sel:
if idx.column() == id1.column():
item = self.qmodel.itemFromIndex(idx)
if item.checkState() != state:
item.setCheckState(state)
def quantHeaderClicked(self, col):
from PySide import QtCore
sel = self.form.quantities.selectedIndexes()
state = None
if len(sel) > 7:
for idx in sel:
if idx.column() == col:
item = self.qmodel.itemFromIndex(idx)
if state is None:
# take the state to apply from the first selected item
state = QtCore.Qt.Checked
if item.checkState():
state = QtCore.Qt.Unchecked
item.setCheckState(state)
def onClickTree(self, index=None):
FreeCADGui.Selection.clearSelection()
sel = self.form.quantities.selectedIndexes()
for index in sel:
if index.column() == 0:
obj = FreeCAD.ActiveDocument.getObject(self.qmodel.itemFromIndex(index).toolTip())
if obj:
FreeCADGui.Selection.addSelection(obj)
FreeCADGui.addCommand("BIM_IfcQuantities", BIM_IfcQuantities())