# *************************************************************************** # * * # * Copyright (c) 2022 Yorik van Havre * # * * # * 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 * # * * # *************************************************************************** """This module contains IFC object definitions""" import FreeCAD translate = FreeCAD.Qt.translate # the property groups below should not be treated as psets NON_PSETS = ["Base", "IFC", "", "Geometry", "Dimension", "Linear/radial dimension", "SectionPlane", "PhysicalProperties"] class ifc_object: """Base class for all IFC-based objects""" def __init__(self, otype=None): self.cached = True # this marks that the object is freshly created and its shape should be taken from cache self.virgin_placement = True # this allows to set the initial placement without triggering any placement change if otype: self.Type = ( otype[0].upper() + otype[1:] ) # capitalize to match Draft standard else: self.Type = "IfcObject" def onBeforeChange(self, obj, prop): if prop == "Schema": self.old_schema = obj.Schema elif prop == "Placement": self.old_placement = obj.Placement def onChanged(self, obj, prop): # link class property to its hidder IfcClass counterpart if prop == "IfcClass" and hasattr(obj, "Class") and obj.Class != obj.IfcClass: obj.Class = obj.IfcClass self.rebuild_classlist(obj, setprops=True) elif prop == "Class" and hasattr(obj, "IfcClass") and obj.Class != obj.IfcClass: obj.IfcClass = obj.Class 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 hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "IFC": if prop not in ["StepId"]: self.edit_attribute(obj, prop) elif prop == "Label": self.edit_attribute(obj, "Name", obj.Label) elif prop == "Text": self.edit_annotation(obj, "Text", "\n".join(obj.Text)) elif prop in ["Start", "End"]: self.edit_annotation(obj, prop) elif prop in ["DisplayLength","DisplayHeight","Depth"]: self.edit_annotation(obj, prop) elif prop == "Placement": if getattr(self, "virgin_placement", False): self.virgin_placement = False elif obj.Placement != getattr(self, "old_placement", None): # print("placement changed for",obj.Label,"to",obj.Placement) self.edit_placement(obj) elif prop == "Modified": if obj.ViewObject: obj.ViewObject.signalChangeIcon() elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "Geometry": self.edit_geometry(obj, prop) elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) not in NON_PSETS: # Treat all property groups outside the default ones as Psets # print("DEBUG: editinog pset prop",prop) self.edit_pset(obj, prop) def onDocumentRestored(self, obj): self.rebuild_classlist(obj) if hasattr(obj, "IfcFilePath"): # once we have loaded the project, recalculate child coin nodes from PySide import QtCore # lazy loading if obj.OutListRecursive: for child in obj.OutListRecursive: if getattr(child, "ShapeMode", None) == "Coin": child.Proxy.cached = True child.touch() else: obj.Proxy.cached = True QtCore.QTimer.singleShot(100, obj.touch) QtCore.QTimer.singleShot(100, obj.Document.recompute) QtCore.QTimer.singleShot(100, self.fit_all) def fit_all(self): """Fits the view""" import FreeCAD if FreeCAD.GuiUp: import FreeCADGui FreeCADGui.SendMsgToActiveView("ViewFit") def rebuild_classlist(self, obj, setprops=False): """rebuilds the list of Class enum property according to current class""" from nativeifc import ifc_tools # lazy import obj.Class = [obj.IfcClass] obj.Class = ifc_tools.get_ifc_classes(obj, obj.IfcClass) obj.Class = obj.IfcClass if setprops: ifc_tools.remove_unused_properties(obj) ifc_tools.add_properties(obj) def __getstate__(self): return getattr(self, "Type", None) def __setstate__(self, state): self.loads(state) def dumps(self): return getattr(self, "Type", None) def loads(self, state): if state and hasattr(state, "Type"): self.Type = state return None def execute(self, obj): from nativeifc import ifc_generator # lazy import if obj.isDerivedFrom("Part::Feature"): cached = getattr(self, "cached", False) ifc_generator.generate_geometry(obj, cached=cached) self.cached = False self.rebuild_classlist(obj) def addObject(self, obj, child): if child not in obj.Group: g = obj.Group g.append(child) obj.Group = g def removeObject(self, obj, child): if child in obj.Group: g = obj.Group g.remove(child) obj.Group = g def edit_attribute(self, obj, attribute, value=None): """Edits an attribute of an underlying IFC object""" from nativeifc import ifc_tools # lazy import if not value: value = obj.getPropertyByName(attribute) ifcfile = ifc_tools.get_ifcfile(obj) elt = ifc_tools.get_ifc_element(obj, ifcfile) if elt: result = ifc_tools.set_attribute(ifcfile, elt, attribute, value) if result: if hasattr(result, "id") and (result.id() != obj.StepId): obj.StepId = result.id() def edit_annotation(self, obj, attribute, value=None): """Edits an attribute of an underlying IFC annotation""" from nativeifc import ifc_tools # lazy import from nativeifc import ifc_export if not value: if hasattr(obj, attribute): value = obj.getPropertyByName(attribute) ifcfile = ifc_tools.get_ifcfile(obj) elt = ifc_tools.get_ifc_element(obj, ifcfile) if elt: if attribute == "Text": text = ifc_export.get_text(elt) if text: ifc_tools.set_attribute(ifcfile, text, "Literal", value) elif attribute in ["Start", "End"]: dim = ifc_export.get_dimension(elt) if dim: rep = dim[0] for curve in rep.Items: if not hasattr(curve, "Elements"): # this is a TextLiteral for the dimension text - skip it continue for sub in curve.Elements: if sub.is_a("IfcIndexedPolyCurve"): points = sub.Points value = list(points.CoordList) is2d = "2D" in points.is_a() if attribute == "Start": value[0] = ifc_export.get_scaled_point(obj.Start, ifcfile, is2d) else: value[-1] = ifc_export.get_scaled_point(obj.End, ifcfile, is2d) ifc_tools.set_attribute(ifcfile, points, "CoordList", value) else: print("DEBUG: unknown dimension curve type:",sub) elif attribute in ["DisplayLength","DisplayHeight","Depth"]: l = w = h = 1000.0 if obj.ViewObject: if obj.ViewObject.DisplayLength.Value: l = ifc_export.get_scaled_value(obj.ViewObject.DisplayLength.Value, ifcfile) if obj.ViewObject.DisplayHeight.Value: w = ifc_export.get_scaled_value(obj.ViewObject.DisplayHeight.Value, ifcfile) if obj.Depth.Value: h = ifc_export.get_scaled_value(obj.Depth.Value, ifcfile) if elt.Representation.Representations: for rep in elt.Representation.Representations: for item in rep.Items: if item.is_a("IfcCsgSolid"): if item.TreeRootExpression.is_a("IfcBlock"): block = item.TreeRootExpression loc = block.Position.Location ifc_tools.set_attribute(ifcfile, block, "XLength", l) ifc_tools.set_attribute(ifcfile, block, "YLength", w) ifc_tools.set_attribute(ifcfile, block, "ZLength", h) ifc_tools.set_attribute(ifcfile, loc, "Coordinates", (-l/2, -h/2, -h)) def edit_geometry(self, obj, prop): """Edits a geometry property of an object""" from nativeifc import ifc_geometry # lazy loading from nativeifc import ifc_tools # lazy import result = ifc_geometry.set_geom_property(obj, prop) if result: obj.touch() def edit_schema(self, obj, schema): """Changes the schema of an IFC document""" from nativeifc import ifc_tools # lazy import ifcfile = ifc_tools.get_ifcfile(obj) if not ifcfile: return if not getattr(self, "old_schema", None): return if schema != ifcfile.wrapped_data.schema_name(): # set obj.Proxy.silent = True to disable the schema change warning if obj.ViewObject and not getattr(self, "silent", False): if not obj.ViewObject.Proxy.schema_warning(): return ifcfile, migration_table = ifc_tools.migrate_schema(ifcfile, schema) self.ifcfile = ifcfile for old_id, new_id in migration_table.items(): child = [ o for o in obj.OutListRecursive if getattr(o, "StepId", None) == old_id ] if len(child) == 1: child[0].StepId = new_id def edit_placement(self, obj): """Syncs the internal IFC placement""" from nativeifc import ifc_tools # lazy import ifc_tools.set_placement(obj) def edit_pset(self, obj, prop): """Edits a Pset value""" from nativeifc import ifc_psets # lazy import ifc_psets.edit_pset(obj, prop) def edit_group(self, obj): """Edits the children list""" from nativeifc import ifc_tools # lazy import from nativeifc import ifc_layers if obj.Class in [ "IfcPresentationLayerAssignment", "IfcPresentationLayerWithStyle", ]: ifcfile = ifc_tools.get_ifcfile(obj) if not ifcfile: return newlist = [] for child in obj.Group: if ( not getattr(child, "StepId", None) or ifc_tools.get_ifcfile(child) != ifcfile ): print( "DEBUG: Not an IFC object. Removing", child.Label, "from layer", obj.Label, ) else: # print("DEBUG: adding", child.Label, "to layer", obj.Label) newlist.append(child) ifc_layers.add_to_layer(child, obj) 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 def get_section_data(self, obj): """Returns two things: a list of objects and a cut plane""" from nativeifc import ifc_tools # lazy import import Part if not obj.IfcClass == "IfcAnnotation": return [], None if obj.ObjectType != "DRAWING": return [], None objs = getattr(obj, "Objects", []) if not objs: # no object defined, we automatically use the project objs = [] proj = ifc_tools.get_project(obj) if isinstance(proj, FreeCAD.DocumentObject): objs.append(proj) objs.extend(ifc_tools.get_freecad_children(proj)) if objs: s = [] for o in objs: # TODO print a better message if o.ShapeMode != "Shape": s.append(o) if s: FreeCAD.Console.PrintLog("DEBUG: Generating shapes. This might take some time...\n") for o in s: o.ShapeMode = "Shape" o.recompute() l = 1 h = 1 if obj.ViewObject: if hasattr(obj.ViewObject,"DisplayLength"): l = obj.ViewObject.DisplayLength.Value h = obj.ViewObject.DisplayHeight.Value plane = Part.makePlane(l,h,FreeCAD.Vector(l/2,-h/2,0),FreeCAD.Vector(0,0,1)) plane.Placement = obj.Placement return objs, plane else: print("DEBUG: Section plane returned no objects") return [], None class document_object: """Holder for the document's IFC objects""" def __init__(self): pass