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

@@ -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"""