From 1ba6bead9d1296a1b5d537fb034271b545596934 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Thu, 26 Sep 2024 13:18:03 +0200 Subject: [PATCH] BIM: NativeIFC 2D support - texts --- src/Mod/BIM/importers/exportIFC.py | 29 +++++++---- src/Mod/BIM/nativeifc/ifc_export.py | 19 +++++++ src/Mod/BIM/nativeifc/ifc_objects.py | 18 +++++++ src/Mod/BIM/nativeifc/ifc_tools.py | 78 +++++++++++++++++----------- 4 files changed, 104 insertions(+), 40 deletions(-) diff --git a/src/Mod/BIM/importers/exportIFC.py b/src/Mod/BIM/importers/exportIFC.py index e587324a2d..813aa66a7a 100644 --- a/src/Mod/BIM/importers/exportIFC.py +++ b/src/Mod/BIM/importers/exportIFC.py @@ -2443,11 +2443,9 @@ def create_annotation(anno, ifcfile, context, history, preferences): # uses global ifcbin, curvestyles objectType = None - xvc = ifcbin.createIfcDirection((1.0,0.0,0.0)) - zvc = ifcbin.createIfcDirection((0.0,0.0,1.0)) - ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0)) - gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc) - placement = ifcbin.createIfcLocalPlacement(gpl) + ovc = None + zvc = None + xvc = None if anno.isDerivedFrom("Part::Feature"): if Draft.getType(anno) == "Hatch": objectType = "HATCH" @@ -2477,18 +2475,20 @@ def create_annotation(anno, ifcfile, context, history, preferences): elif anno.isDerivedFrom("App::Annotation"): objectType = "TEXT" l = FreeCAD.Vector(anno.Position).multiply(preferences['SCALE_FACTOR']) - pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) + pos = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0)) tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) + ovc = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) s = ";".join(anno.LabelText) txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT") reps = [txt] elif Draft.getType(anno) in ["DraftText","Text"]: objectType = "TEXT" l = FreeCAD.Vector(anno.Placement.Base).multiply(preferences['SCALE_FACTOR']) - pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) - zdir = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1)))) - xdir = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(1,0,0)))) - tpl = ifcbin.createIfcAxis2Placement3D(pos,zdir,xdir) + pos = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0)) + tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) + ovc = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) + zvc = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1)))) + xvc = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(1,0,0)))) alg = "LEFT" if FreeCAD.GuiUp and hasattr(anno.ViewObject,"Justification"): if anno.ViewObject.Justification == "Right": @@ -2546,7 +2546,14 @@ def create_annotation(anno, ifcfile, context, history, preferences): for rep in reps: isi = ifcfile.createIfcStyledItem(rep,[psa],None) break - + if not xvc: + xvc = ifcbin.createIfcDirection((1.0,0.0,0.0)) + if not zvc: + zvc = ifcbin.createIfcDirection((0.0,0.0,1.0)) + if not ovc: + ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0)) + gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc) + placement = ifcbin.createIfcLocalPlacement(gpl) shp = ifcfile.createIfcShapeRepresentation(context,'Annotation','Annotation2D',reps) rep = ifcfile.createIfcProductDefinitionShape(None,None,[shp]) label = anno.Label diff --git a/src/Mod/BIM/nativeifc/ifc_export.py b/src/Mod/BIM/nativeifc/ifc_export.py index 9fda8b7735..569a4cc4f2 100644 --- a/src/Mod/BIM/nativeifc/ifc_export.py +++ b/src/Mod/BIM/nativeifc/ifc_export.py @@ -26,6 +26,7 @@ import ifcopenshell from importers import exportIFC from importers import exportIFCHelper +from importers import importIFCHelper from nativeifc import ifc_tools @@ -116,6 +117,17 @@ def is_annotation(obj): return False +def get_text(annotation): + """Determines if an IfcAnnotation contains an IfcTextLiteral. + Returns the IfcTextLiteral or None""" + + for rep in annotation.Representation.Representations: + for item in rep.Items: + if item.is_a("IfcTextLiteral"): + return item + return None + + def create_annotation(obj, ifcfile): """Adds an IfcAnnotation from the given object to the given IFC file""" @@ -144,3 +156,10 @@ def get_history(ifcfile): # IFC4 allows to not write any history history = None return history + + +def get_placement(ifcelement, ifcfile): + """Returns a FreeCAD placement from an IFC placement""" + + s = 0.001 / ifcopenshell.util.unit.calculate_unit_scale(ifcfile) + return importIFCHelper.getPlacement(ifcelement, scaling=s) diff --git a/src/Mod/BIM/nativeifc/ifc_objects.py b/src/Mod/BIM/nativeifc/ifc_objects.py index 5f22990942..d67610dd2e 100644 --- a/src/Mod/BIM/nativeifc/ifc_objects.py +++ b/src/Mod/BIM/nativeifc/ifc_objects.py @@ -64,6 +64,8 @@ class ifc_object: 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 == "Placement": if getattr(self, "virgin_placement", False): self.virgin_placement = False @@ -169,6 +171,22 @@ class ifc_object: 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: + 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: + result = ifc_tools.set_attribute(ifcfile, text, "Literal", value) + def edit_geometry(self, obj, prop): """Edits a geometry property of an object""" diff --git a/src/Mod/BIM/nativeifc/ifc_tools.py b/src/Mod/BIM/nativeifc/ifc_tools.py index fcde65740a..7f62411612 100644 --- a/src/Mod/BIM/nativeifc/ifc_tools.py +++ b/src/Mod/BIM/nativeifc/ifc_tools.py @@ -47,6 +47,8 @@ from nativeifc import ifc_layers from nativeifc import ifc_status from nativeifc import ifc_export +from draftviewproviders import view_layer + SCALE = 1000.0 # IfcOpenShell works in meters, FreeCAD works in mm SHORT = False # If True, only Step ID attribute is created ROUND = 8 # rounding value for placements @@ -243,7 +245,7 @@ def api_run(*args, **kwargs): return result -def create_object(ifcentity, document, ifcfile, shapemode=0): +def create_object(ifcentity, document, ifcfile, shapemode=0, objecttype=None): """Creates a FreeCAD object from an IFC entity""" exobj = get_object(ifcentity, document) @@ -253,7 +255,7 @@ def create_object(ifcentity, document, ifcfile, shapemode=0): ifcentity.id(), ifcentity.is_a(), ifcentity.Name ) FreeCAD.Console.PrintLog(s) - obj = add_object(document) + obj = add_object(document, otype=objecttype) add_properties(obj, ifcfile, ifcentity, shapemode=shapemode) ifc_layers.add_layers(obj, ifcentity, ifcfile) if FreeCAD.GuiUp: @@ -460,37 +462,41 @@ def can_expand(obj, ifcfile=None): def add_object(document, otype=None, oname="IfcObject"): """adds a new object to a FreeCAD document. - otype can be 'project', 'group', 'material', 'layer' or None (normal object)""" + otype can be: + 'project', + 'group', + 'material', + 'layer', + 'text', + or anything else for a standard IFC object""" if not document: return None - proxy = ifc_objects.ifc_object(otype) - if otype == "group": - proxy = None - ftype = "App::DocumentObjectGroupPython" - elif otype == "material": - ftype = "App::MaterialObjectPython" + if otype == "text": + obj = Draft.make_text("") + obj.Proxy = ifc_objects.ifc_object(otype) elif otype == "layer": - ftype = "App::FeaturePython" - else: - ftype = "Part::FeaturePython" - if otype == "project": - vp = ifc_viewproviders.ifc_vp_document() + proxy = ifc_objects.ifc_object(otype) + obj = document.addObject("App::FeaturePython", oname, proxy, None, False) + if obj.ViewObject: + view_layer.ViewProviderLayer(obj.ViewObject) + obj.ViewObject.addProperty("App::PropertyBool", "HideChildren", "Layer") + obj.ViewObject.HideChildren = True elif otype == "group": - vp = ifc_viewproviders.ifc_vp_group() + vproxy = ifc_viewproviders.ifc_vp_group() + obj = document.addObject("App::DocumentObjectGroupPython", oname, None, vproxy, False) elif otype == "material": - vp = ifc_viewproviders.ifc_vp_material() - elif otype == "layer": - vp = None - else: - vp = ifc_viewproviders.ifc_vp_object() - obj = document.addObject(ftype, oname, proxy, vp, False) - if obj.ViewObject and otype == "layer": - from draftviewproviders import view_layer # lazy import - - view_layer.ViewProviderLayer(obj.ViewObject) - obj.ViewObject.addProperty("App::PropertyBool", "HideChildren", "Layer") - obj.ViewObject.HideChildren = True + proxy = ifc_objects.ifc_object(otype) + vproxy = ifc_viewproviders.ifc_vp_material() + obj = document.addObject("App::MaterialObjectPython", oname, proxy, vproxy, False) + elif otype == "project": + proxy = ifc_objects.ifc_object(otype) + vproxy = ifc_viewproviders.ifc_vp_document() + obj = document.addObject("Part::FeaturePython", oname, proxy, vproxy, False) + else: # default case, standard IFC object + proxy = ifc_objects.ifc_object(otype) + vproxy = ifc_viewproviders.ifc_vp_object() + obj = document.addObject("Part::FeaturePython", oname, proxy, vproxy, False) return obj @@ -621,6 +627,17 @@ def add_properties( obj.addProperty("App::PropertyString", attr, "IFC") if value is not None: setattr(obj, attr, str(value)) + # annotation properties + if ifcentity.is_a("IfcAnnotation"): + text = ifc_export.get_text(ifcentity) + if text: + # the two props below are already taken care of, normally + if "Placement" not in obj.PropertiesList: + obj.addProperty("App::PropertyPlacement", "Placement", "Base") + if "Text" not in obj.PropertiesList: + obj.addProperty("App::PropertyStringList", "Text", "Base") + obj.Text = [text.Literal] + obj.Placement = ifc_export.get_placement(ifcentity.ObjectPlacement, ifcfile) # link Label2 and Description if "Description" in obj.PropertiesList and hasattr(obj, "setExpression"): obj.setExpression("Label2", "Description") @@ -1008,12 +1025,15 @@ def aggregate(obj, parent, mode=None): ifcclass = None if mode == "opening": ifcclass = "IfcOpeningElement" + objecttype = None if ifc_export.is_annotation(obj): product = ifc_export.create_annotation(obj, ifcfile) + if Draft.get_type(obj) in ["DraftText","Text"]: + objecttype = "text" else: product = ifc_export.create_product(obj, parent, ifcfile, ifcclass) shapemode = getattr(parent, "ShapeMode", DEFAULT_SHAPEMODE) - newobj = create_object(product, obj.Document, ifcfile, shapemode) + newobj = create_object(product, obj.Document, ifcfile, shapemode, objecttype) new = True create_relationship(obj, newobj, parent, product, ifcfile, mode) base = getattr(obj, "Base", None) @@ -1317,7 +1337,7 @@ def remove_ifc_element(obj,delete_obj=False): def get_orphan_elements(ifcfile): """returns a list of orphan products in an ifcfile""" - products = ifcfile.by_type("IfcElement") + products = ifcfile.by_type("IfcProduct") products = [p for p in products if not p.Decomposes] products = [p for p in products if not p.ContainedInStructure] products = [