BIM: NativeIFC: Support for types

This commit is contained in:
Yorik van Havre
2024-12-03 14:22:26 +01:00
committed by Yorik van Havre
parent 9bd4d2466e
commit d0cf727b7a
7 changed files with 335 additions and 86 deletions

View File

@@ -187,6 +187,7 @@ SET(nativeifc_SRCS
nativeifc/ifc_viewproviders.py
nativeifc/__init__.py
nativeifc/ifc_openshell.py
nativeifc/ifc_types.py
)
SOURCE_GROUP("" FILES ${Arch_SRCS})

View File

@@ -1,23 +1,5 @@
<RCC>
<qresource>
<file>icons/IFC/IfcBeam.svg</file>
<file>icons/IFC/IfcBuilding.svg</file>
<file>icons/IFC/IfcBuildingStorey.svg</file>
<file>icons/IFC/IfcColumn.svg</file>
<file>icons/IFC/IfcCovering.svg</file>
<file>icons/IFC/IfcDoor.svg</file>
<file>icons/IFC/IfcFooting.svg</file>
<file>icons/IFC/IfcMember.svg</file>
<file>icons/IFC/IfcPile.svg</file>
<file>icons/IFC/IfcPlate.svg</file>
<file>icons/IFC/IfcRailing.svg</file>
<file>icons/IFC/IfcRamp.svg</file>
<file>icons/IFC/IfcRoof.svg</file>
<file>icons/IFC/IfcSite.svg</file>
<file>icons/IFC/IfcSlab.svg</file>
<file>icons/IFC/IfcStair.svg</file>
<file>icons/IFC/IfcWall.svg</file>
<file>icons/IFC/IfcWindow.svg</file>
<file>icons/Arch_3Views.svg</file>
<file>icons/Arch_Add.svg</file>
<file>icons/Arch_Axis.svg</file>
@@ -163,66 +145,24 @@
<file>icons/techdraw-ArchView.svg</file>
<file>icons/techdraw-PageDefault.svg</file>
<file>icons/warning.svg</file>
<file>ui/ArchMaterial.ui</file>
<file>ui/ArchMultiMaterial.ui</file>
<file>ui/ArchNest.ui</file>
<file>ui/ArchSchedule.ui</file>
<file>ui/BimServerTaskPanel.ui</file>
<file>ui/DialogBimServerLogin.ui</file>
<file>ui/DialogDisplayText.ui</file>
<file>ui/GitTaskPanel.ui</file>
<file>ui/ParametersBeam.svg</file>
<file>ui/ParametersDent.svg</file>
<file>ui/ParametersDoorGlass.svg</file>
<file>ui/ParametersDoorSimple.svg</file>
<file>ui/ParametersIbeam.svg</file>
<file>ui/ParametersOpening.svg</file>
<file>ui/ParametersPanel.svg</file>
<file>ui/ParametersPillar.svg</file>
<file>ui/ParametersSlab.svg</file>
<file>ui/ParametersStairs.svg</file>
<file>ui/ParametersWindowDouble.svg</file>
<file>ui/ParametersWindowFixed.svg</file>
<file>ui/ParametersWindowSimple.svg</file>
<file>ui/ParametersWindowStash.svg</file>
<file>ui/dialogAddPSet.ui</file>
<file>ui/dialogAddProperty.ui</file>
<file>ui/dialogClasses.ui</file>
<file>ui/dialogClassification.ui</file>
<file>ui/dialogConvertDocument.ui</file>
<file>ui/dialogCreateProject.ui</file>
<file>ui/dialogCustomProperties.ui</file>
<file>ui/dialogDiff.ui</file>
<file>ui/dialogExport.ui</file>
<file>ui/dialogIfcElements.ui</file>
<file>ui/dialogIfcProperties.ui</file>
<file>ui/dialogIfcPropertiesRedux.ui</file>
<file>ui/dialogIfcQuantities.ui</file>
<file>ui/dialogImport.ui</file>
<file>ui/dialogLayersIFC.ui</file>
<file>ui/dialogLibrary.ui</file>
<file>ui/dialogListWidget.ui</file>
<file>ui/dialogMaterialChooser.ui</file>
<file>ui/dialogNudgeValue.ui</file>
<file>ui/dialogPhases.ui</file>
<file>ui/dialogPreflight.ui</file>
<file>ui/dialogPreflightResults.ui</file>
<file>ui/dialogProjectManager.ui</file>
<file>ui/dialogQuantitySurveying.ui</file>
<file>ui/dialogReorder.ui</file>
<file>ui/dialogSetup.ui</file>
<file>ui/dialogSpaces.ui</file>
<file>ui/dialogTree.ui</file>
<file>ui/dialogTutorial.ui</file>
<file>ui/dialogViews.ui</file>
<file>ui/dialogWelcome.ui</file>
<file>ui/dialogWindows.ui</file>
<file>ui/preferences-arch.ui</file>
<file>ui/preferences-archdefaults.ui</file>
<file>ui/preferences-dae.ui</file>
<file>ui/preferences-ifc-export.ui</file>
<file>ui/preferences-ifc.ui</file>
<file>ui/preferencesNativeIFC.ui</file>
<file>icons/IFC/IfcBeam.svg</file>
<file>icons/IFC/IfcBuilding.svg</file>
<file>icons/IFC/IfcBuildingStorey.svg</file>
<file>icons/IFC/IfcColumn.svg</file>
<file>icons/IFC/IfcCovering.svg</file>
<file>icons/IFC/IfcDoor.svg</file>
<file>icons/IFC/IfcFooting.svg</file>
<file>icons/IFC/IfcMember.svg</file>
<file>icons/IFC/IfcPile.svg</file>
<file>icons/IFC/IfcPlate.svg</file>
<file>icons/IFC/IfcRailing.svg</file>
<file>icons/IFC/IfcRamp.svg</file>
<file>icons/IFC/IfcRoof.svg</file>
<file>icons/IFC/IfcSite.svg</file>
<file>icons/IFC/IfcSlab.svg</file>
<file>icons/IFC/IfcStair.svg</file>
<file>icons/IFC/IfcWall.svg</file>
<file>icons/IFC/IfcWindow.svg</file>
<file>translations/Arch_af.qm</file>
<file>translations/Arch_ar.qm</file>
<file>translations/Arch_be.qm</file>
@@ -266,5 +206,66 @@
<file>translations/Arch_vi.qm</file>
<file>translations/Arch_zh-CN.qm</file>
<file>translations/Arch_zh-TW.qm</file>
<file>ui/ArchMaterial.ui</file>
<file>ui/ArchMultiMaterial.ui</file>
<file>ui/ArchNest.ui</file>
<file>ui/ArchSchedule.ui</file>
<file>ui/BimServerTaskPanel.ui</file>
<file>ui/DialogBimServerLogin.ui</file>
<file>ui/DialogDisplayText.ui</file>
<file>ui/GitTaskPanel.ui</file>
<file>ui/ParametersBeam.svg</file>
<file>ui/ParametersDent.svg</file>
<file>ui/ParametersDoorGlass.svg</file>
<file>ui/ParametersDoorSimple.svg</file>
<file>ui/ParametersIbeam.svg</file>
<file>ui/ParametersOpening.svg</file>
<file>ui/ParametersPanel.svg</file>
<file>ui/ParametersPillar.svg</file>
<file>ui/ParametersSlab.svg</file>
<file>ui/ParametersStairs.svg</file>
<file>ui/ParametersWindowDouble.svg</file>
<file>ui/ParametersWindowFixed.svg</file>
<file>ui/ParametersWindowSimple.svg</file>
<file>ui/ParametersWindowStash.svg</file>
<file>ui/dialogAddPSet.ui</file>
<file>ui/dialogAddProperty.ui</file>
<file>ui/dialogClasses.ui</file>
<file>ui/dialogClassification.ui</file>
<file>ui/dialogConvertDocument.ui</file>
<file>ui/dialogConvertType.ui</file>
<file>ui/dialogCreateProject.ui</file>
<file>ui/dialogCustomProperties.ui</file>
<file>ui/dialogDiff.ui</file>
<file>ui/dialogExport.ui</file>
<file>ui/dialogIfcElements.ui</file>
<file>ui/dialogIfcProperties.ui</file>
<file>ui/dialogIfcPropertiesRedux.ui</file>
<file>ui/dialogIfcQuantities.ui</file>
<file>ui/dialogImport.ui</file>
<file>ui/dialogLayersIFC.ui</file>
<file>ui/dialogLibrary.ui</file>
<file>ui/dialogListWidget.ui</file>
<file>ui/dialogMaterialChooser.ui</file>
<file>ui/dialogNudgeValue.ui</file>
<file>ui/dialogPhases.ui</file>
<file>ui/dialogPreflight.ui</file>
<file>ui/dialogPreflightResults.ui</file>
<file>ui/dialogProjectManager.ui</file>
<file>ui/dialogQuantitySurveying.ui</file>
<file>ui/dialogReorder.ui</file>
<file>ui/dialogSetup.ui</file>
<file>ui/dialogSpaces.ui</file>
<file>ui/dialogTree.ui</file>
<file>ui/dialogTutorial.ui</file>
<file>ui/dialogViews.ui</file>
<file>ui/dialogWelcome.ui</file>
<file>ui/dialogWindows.ui</file>
<file>ui/preferences-arch.ui</file>
<file>ui/preferences-archdefaults.ui</file>
<file>ui/preferences-dae.ui</file>
<file>ui/preferences-ifc-export.ui</file>
<file>ui/preferences-ifc.ui</file>
<file>ui/preferencesNativeIFC.ui</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>448</width>
<height>161</height>
</rect>
</property>
<property name="windowTitle">
<string>Convert to IFC type</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>This object will be converted to a %1 type. Types can be used to give common attributes and properties to several objects at once.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkKeepObject">
<property name="text">
<string>Keep original object. The object will adopt the new type</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -22,6 +22,9 @@
"""This module contains IFC object definitions"""
import FreeCAD
translate = FreeCAD.Qt.translate
class ifc_object:
"""Base class for all IFC-based objects"""
@@ -52,6 +55,8 @@ class ifc_object:
self.rebuild_classlist(obj, setprops=True)
elif prop == "Schema":
self.edit_schema(obj, obj.Schema)
elif prop == "Type":
self.edit_type(obj)
elif prop == "Group":
self.edit_group(obj)
elif obj.getGroupOfProperty(prop) == "IFC":
@@ -70,7 +75,7 @@ class ifc_object:
obj.ViewObject.signalChangeIcon()
elif obj.getGroupOfProperty(prop) == "Geometry":
self.edit_geometry(obj, prop)
elif obj.getGroupOfProperty(prop) not in ["Base", "IFC", "", "Geometry"]:
elif obj.getGroupOfProperty(prop) not in ["Base", "IFC", "", "Geometry", "PhysicalProperties"]:
# Treat all property groups outside the default ones as Psets
# print("DEBUG: editinog pset prop",prop)
self.edit_pset(obj, prop)
@@ -246,6 +251,42 @@ class ifc_object:
if newlist != obj.Group:
obj.Group = newlist
def edit_type(self, obj):
"""Edits the type of this object"""
from nativeifc import ifc_tools # lazy import
from nativeifc import ifc_types
element = ifc_tools.get_ifc_element(obj)
ifcfile = ifc_tools.get_ifcfile(obj)
if not element or not ifcfile:
return
typerel = getattr(element, "IsTypedBy", None)
if obj.Type:
# verify the type is compatible -ex IFcWall in IfcWallType
if obj.Type.Class != element.is_a() + "Type":
t = translate("BIM","Error: Incompatible type")
FreeCAD.Console.PrintError(obj.Label+": "+t+": "+obj.Type.Class+"\n")
obj.Type = None
return
# change type
new_type = ifc_tools.get_ifc_element(obj.Type)
if not new_type:
return
for rel in typerel:
if rel.RelatingType == new_type:
return
# assign the new type
ifc_tools.api_run("type.assign_type",
ifcfile,
related_objects=[element],
relating_type=new_type
)
elif typerel:
# TODO remove type?
# Not doing anything right now because an unset Type property could screw the ifc file
pass
class document_object:
"""Holder for the document's IFC objects"""

View File

@@ -525,6 +525,8 @@ def add_properties(
obj.ShapeMode = shapemode
if not obj.isDerivedFrom("Part::Feature"):
obj.setPropertyStatus("ShapeMode", "Hidden")
if ifcentity.is_a("IfcProduct"):
obj.addProperty("App::PropertyLink", "Type", "IFC")
attr_defs = ifcentity.wrapped_data.declaration().as_entity().all_attributes()
try:
info_ifcentity = ifcentity.get_info()
@@ -1190,7 +1192,7 @@ def get_subvolume(obj):
def create_relationship(old_obj, obj, parent, element, ifcfile, mode=None):
"""Creates a relationship between an IFC object and a parent IFC object"""
if isinstance(parent, FreeCAD.DocumentObject):
if isinstance(parent, (FreeCAD.DocumentObject, FreeCAD.Document)):
parent_element = get_ifc_element(parent)
else:
parent_element = parent
@@ -1373,8 +1375,9 @@ def migrate_schema(ifcfile, schema):
return newfile, table
def remove_ifc_element(obj):
"""removes the IFC data associated with an object"""
def remove_ifc_element(obj,delete_obj=False):
"""removes the IFC data associated with an object.
If delete_obj is True, the FreeCAD object is also deleted"""
# This function can become pure IFC
@@ -1382,6 +1385,8 @@ def remove_ifc_element(obj):
element = get_ifc_element(obj)
if ifcfile and element:
api_run("root.remove_product", ifcfile, product=element)
if delete_obj:
obj.Document.removeObject(obj.Name)
return True
return False

View File

@@ -0,0 +1,93 @@
# ***************************************************************************
# * *
# * 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 General Public License (GPL) *
# * as published by the Free Software Foundation; either version 3 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 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 *
# * *
# ***************************************************************************
"""Diffing tool for NativeIFC project objects"""
import FreeCAD
from nativeifc import ifc_tools
translate = FreeCAD.Qt.translate
def show_type(obj):
"""Adds the types of that object as FreeCAD objects"""
element = ifc_tools.get_ifc_element(obj)
typerel = getattr(element, "IsTypedBy", None)
project = ifc_tools.get_project(obj)
ifcfile = ifc_tools.get_ifcfile(obj)
if not all([element, typerel, project, ifcfile]):
return
for rel in typerel:
typelt = rel.RelatingType
if typelt:
typeobj = ifc_tools.create_object(typelt, obj.Document, ifcfile)
ifc_tools.get_group(project, "IfcTypesGroup").addObject(typeobj)
obj.Type = typeobj
def is_typable(obj):
"""Checks if an object can become a type"""
element = ifc_tools.get_ifc_element(obj)
ifcfile = ifc_tools.get_ifcfile(obj)
if not element or not ifcfile:
return False
type_class = element.is_a() + "Type"
schema = ifcfile.wrapped_data.schema_name()
schema = ifc_tools.ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema)
try:
declaration = schema.declaration_by_name(type_class)
except RuntimeError:
return False
return True
def convert_to_type(obj, keep_object=False):
"""Converts an object to a type. If keep_object is
True, the original object is kept (and adopts the new type)."""
if not is_typable(obj):
return
if not getattr(obj, "Shape", None):
return
if FreeCAD.GuiUp:
import FreeCADGui
dlg = FreeCADGui.PySideUic.loadUi(":/ui/dialogConvertType.ui")
result = dlg.exec_()
if not result:
return
keep_object = dlg.checkKeepObject.isChecked()
element = ifc_tools.get_ifc_element(obj)
ifcfile = ifc_tools.get_ifcfile(obj)
project = ifc_tools.get_project(obj)
if not element or not ifcfile or not project:
return
type_element = ifc_tools.api_run("root.copy_class", ifcfile, product=element)
type_element = ifc_tools.api_run("root.reassign_class", ifcfile, product=type_element, ifc_class=obj.Class+"Type")
type_obj = ifc_tools.create_object(type_element, obj.Document, ifcfile)
if keep_object:
obj.Type = type_obj
else:
ifc_tools.remove_ifc_element(obj, delete_obj=True)
ifc_tools.get_group(project, "IfcTypesGroup").addObject(type_obj)

View File

@@ -71,8 +71,8 @@ class ifc_vp_object:
def getIcon(self):
from PySide import QtCore, QtGui # lazy import
rclass = self.Object.IfcClass.replace("StandardCase","")
rclass = self.Object.IfcClass.replace("StandardCase","")
ifcicon = ":/icons/IFC/" + rclass + ".svg"
if QtCore.QFile.exists(ifcicon):
if getattr(self, "ifcclass", "") != rclass:
@@ -95,6 +95,7 @@ class ifc_vp_object:
from nativeifc import ifc_tools # lazy import
from nativeifc import ifc_psets
from nativeifc import ifc_materials
from nativeifc import ifc_types
from PySide import QtCore, QtGui # lazy import
if FreeCADGui.activeWorkbench().name() != 'BIMWorkbench':
@@ -150,11 +151,14 @@ class ifc_vp_object:
action_material = QtGui.QAction(icon, "Load material",menu)
action_material.triggered.connect(self.addMaterial)
actions.append(action_material)
if ifc_types.is_typable(self.Object):
action_type = QtGui.QAction(icon, "Convert to type", menu)
action_type.triggered.connect(self.convertToType)
actions.append(action_type)
if actions:
ifc_menu = QtGui.QMenu("IFC")
ifc_menu.setIcon(icon)
for a in actions:
ifc_menu.addAction(a)
ifc_menu.addActions(actions)
menu.addMenu(ifc_menu)
# generic actions
@@ -362,6 +366,11 @@ class ifc_vp_object:
self.Object.Document.recompute()
def doubleClicked(self, vobj):
"""On double-click"""
self.expandProperties(vobj)
def expandProperties(self, vobj):
"""Expands everything that needs to be expanded"""
from nativeifc import ifc_geometry # lazy import
@@ -369,12 +378,14 @@ class ifc_vp_object:
from nativeifc import ifc_psets # lazy import
from nativeifc import ifc_materials # lazy import
from nativeifc import ifc_layers # lazy import
from nativeifc import ifc_types # lazy import
# generic data loading
ifc_geometry.add_geom_properties(vobj.Object)
ifc_psets.show_psets(vobj.Object)
ifc_materials.show_material(vobj.Object)
ifc_layers.add_layers(vobj.Object)
ifc_types.show_type(vobj.Object)
# expand children
if self.hasChildren(vobj.Object):
@@ -390,6 +401,16 @@ class ifc_vp_object:
return True
return None
def convertToType(self):
"""Converts this object to a type"""
if not hasattr(self, "Object"):
return
from nativeifc import ifc_types
ifc_types.convert_to_type(self.Object)
self.Object.Document.recompute()
class ifc_vp_document(ifc_vp_object):
"""View provider for the IFC document object"""