freecad-cam/Mod/BIM/nativeifc/ifc_tree.py
2026-02-01 01:59:24 +01:00

230 lines
9.1 KiB
Python

# ***************************************************************************
# * *
# * Copyright (c) 2023 Yorik van Havre <yorik@uncreated.net> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program 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 Library General Public License for more details. *
# * *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
# * *
# ***************************************************************************
"""This NativeIFC module handles the retrieval and display of geometry compositions of objects"""
import FreeCAD
TAB = 2
def get_geometry_tree(element, prefix=""):
"""Returns a list of elements representing an object's representation"""
result = [prefix + str(element)]
prefix += TAB * " "
if getattr(element, "Representation", None):
reps = element.Representation
result.append(prefix + str(reps))
prefix += prefix
for rep in reps.Representations:
result.extend(get_geometry_tree(rep, prefix))
elif getattr(element, "Items", None):
for it in element.Items:
result.extend(get_geometry_tree(it, prefix))
elif element.is_a("IfcPolyline"):
for p in element.Points:
result.append(prefix + str(p))
elif element.is_a("IfcExtrudedAreaSolid"):
result.append(prefix + str(element.ExtrudedDirection))
result.extend(get_geometry_tree(element.SweptArea, prefix))
elif element.is_a("IfcArbitraryClosedProfileDef"):
result.extend(get_geometry_tree(element.OuterCurve, prefix))
elif element.is_a("IfcArbitraryProfileDefWithVoids"):
result.extend(get_geometry_tree(element.OuterCurve, prefix))
for inn in element.InnerCurves:
result.extend(get_geometry_tree(inn, prefix))
elif element.is_a("IfcMappedItem"):
result.extend(get_geometry_tree(element.MappingSource[1], prefix))
elif element.is_a("IfcBooleanClippingResult"):
result.extend(get_geometry_tree(element.FirstOperand, prefix))
result.extend(get_geometry_tree(element.SecondOperand, prefix))
elif element.is_a("IfcBooleanResult"):
result.extend(get_geometry_tree(element.FirstOperand, prefix))
result.extend(get_geometry_tree(element.SecondOperand, prefix))
elif element.is_a("IfcHalfSpaceSolid"):
result.extend(get_geometry_tree(element.BaseSurface, prefix))
return result
def print_geometry_tree(element):
"""Same as get_geometry_tree but printed"""
for line in get_geometry_tree(element):
print(line)
def show_geometry_tree(element):
"""Same as get_geometry_tree but in a Qt dialog"""
import Arch_rc
import FreeCADGui # lazy import
from nativeifc import ifc_tools
from PySide import QtGui, QtWidgets
if isinstance(element, FreeCAD.DocumentObject):
element = ifc_tools.get_ifc_element(element)
if not element:
return
dlg = FreeCADGui.PySideUic.loadUi(":/ui/dialogTree.ui")
tops = {}
for line in get_geometry_tree(element):
psize = (len(line) - len(line.lstrip())) / TAB
wline = QtWidgets.QTreeWidgetItem([line.lstrip()])
if not psize:
dlg.geomtree.addTopLevelItem(wline)
else:
parent = tops[psize - 1]
parent.addChild(wline)
tops[psize] = wline
dlg.geomtree.expandAll()
dlg.setWindowTitle(dlg.windowTitle() + " " + element.Name)
dlg.geomtree.currentItemChanged.connect(show_properties)
dlg.proptree.itemChanged.connect(edit_property)
result = dlg.exec_()
if result:
props = [r.split("::") for r in dlg.proptree.toolTip().split(";;;;") if r]
if not props:
return
obj = ifc_tools.get_object(element)
ifcfile = ifc_tools.get_ifcfile(obj)
modified = False
for prop in props:
elt = ifcfile[int(prop[0])]
attrib = prop[1]
if attrib not in dir(elt):
print("DEBUG: Unknown attribute:", attrib)
continue
value = prop[3]
if isfloat(getattr(elt, attrib)):
try:
value = float(value)
except:
print("DEBUG: wrong value for", attrib, ":", value)
continue
ifc_tools.set_attribute(ifcfile, elt, attrib, value)
modified = True
if modified:
obj.touch()
proj = ifc_tools.get_project(obj)
proj.Modified = True
obj.Document.recompute()
def isfloat(s):
"""Tells if the given string is a number"""
try:
float(s)
except ValueError:
return False
print(s)
return True
def show_properties(current, previous):
"""Displays object properties"""
import FreeCADGui
from nativeifc import ifc_tools # lazy loading
from PySide import QtCore, QtGui, QtWidgets
ifcid = int(current.text(0).split("=", 1)[0].strip(" ").strip("#"))
sel = FreeCADGui.Selection.getSelection()
if len(sel) != 1:
return
obj = sel[0]
ifcfile = ifc_tools.get_ifcfile(obj)
if not ifcfile:
return
elt = ifcfile[ifcid]
box = current.treeWidget().parentWidget().widget(1)
box.setTitle("#" + str(ifcid) + ": " + elt.is_a())
props = [p for p in dir(elt) if p[0].isupper()]
# allow only numeric values?
# props = [p for p in props if isfloat(str(getattr(elt,p)))]
props = [p for p in props if not str(getattr(elt, p)).startswith("#")]
props = [p for p in props if not str(getattr(elt, p)).startswith("(")]
props = [
p for p in props if p not in ["Position", "LayerAssignments", "StyledByItem"]
]
proptree = box.children()[0].itemAt(0).widget()
proptree.clear()
proptree.setHorizontalHeaderLabels(["Property", "Value"])
proptree.setRowCount(len(props))
for i, prop in enumerate(props):
value = str(getattr(elt, prop))
if value == "None":
value = ""
r1 = QtWidgets.QTableWidgetItem(prop)
r1.setFlags(QtCore.Qt.ItemIsSelectable)
proptree.setItem(i, 0, r1)
r2 = QtWidgets.QTableWidgetItem(value)
r2.setToolTip(value)
if value.startswith("#") or value.startswith("(") or prop == "GlobalId":
r2.setFlags(QtCore.Qt.ItemIsSelectable)
proptree.setItem(i, 1, r2)
if "Position" in dir(elt):
i = proptree.rowCount()
proptree.setRowCount(i + 6)
position = FreeCAD.Vector(elt.Position.Location.Coordinates)
axis = FreeCAD.Vector(elt.Position.Axis.DirectionRatios)
xref = FreeCAD.Vector(elt.Position.RefDirection.DirectionRatios)
rotation = FreeCAD.Rotation(axis, xref, FreeCAD.Vector(), "ZXY").toEulerAngles(
"XYZ"
)
rotation = FreeCAD.Vector(rotation)
for c in ["x", "y", "z"]:
r1 = QtWidgets.QTableWidgetItem("Position " + c.upper())
r1.setFlags(QtCore.Qt.ItemIsSelectable)
proptree.setItem(i, 0, r1)
v = str(getattr(position, c))
r2 = QtWidgets.QTableWidgetItem(v)
r2.setToolTip(v)
proptree.setItem(i, 1, r2)
i += 1
for c in ["x", "y", "z"]:
r1 = QtWidgets.QTableWidgetItem("Rotation " + c.upper())
r1.setFlags(QtCore.Qt.ItemIsSelectable)
proptree.setItem(i, 0, r1)
v = str(getattr(rotation, c))
r2 = QtWidgets.QTableWidgetItem(v)
r2.setToolTip(v)
proptree.setItem(i, 1, r2)
i += 1
def edit_property(item):
"""Edits the value of a property"""
if item.toolTip() and (item.text() != item.toolTip()):
old = item.toolTip()
new = item.text()
table = item.tableWidget()
prop = table.item(item.row(), 0).text()
dlg = table.parent().parent().parent()
line = dlg.geomtree.currentItem().text(0)
ifcid = line.split("=", 1)[0].strip(" ").strip("#")
strid = ";;;;" + "::".join([str(i) for i in [ifcid, prop, old, new]])
dlg.proptree.setToolTip(dlg.proptree.toolTip() + strid)