From 9c9d451ac68b7cb538f66bf5b2e45197f5265ba3 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Wed, 18 Sep 2024 10:49:22 +0200 Subject: [PATCH] BIM: NativeIFC 2D support - basic import/export + linework annotations --- src/Mod/BIM/CMakeLists.txt | 1 + src/Mod/BIM/importers/exportIFC.py | 243 +++++++++++++------------ src/Mod/BIM/nativeifc/ifc_export.py | 146 +++++++++++++++ src/Mod/BIM/nativeifc/ifc_generator.py | 82 ++++++++- src/Mod/BIM/nativeifc/ifc_status.py | 5 +- src/Mod/BIM/nativeifc/ifc_tools.py | 89 +-------- 6 files changed, 356 insertions(+), 210 deletions(-) create mode 100644 src/Mod/BIM/nativeifc/ifc_export.py diff --git a/src/Mod/BIM/CMakeLists.txt b/src/Mod/BIM/CMakeLists.txt index df1dcb5c75..1e50d4be66 100644 --- a/src/Mod/BIM/CMakeLists.txt +++ b/src/Mod/BIM/CMakeLists.txt @@ -189,6 +189,7 @@ SET(nativeifc_SRCS nativeifc/__init__.py nativeifc/ifc_openshell.py nativeifc/ifc_types.py + nativeifc/ifc_export.py ) SOURCE_GROUP("" FILES ${Arch_SRCS}) diff --git a/src/Mod/BIM/importers/exportIFC.py b/src/Mod/BIM/importers/exportIFC.py index 7088e923bc..e587324a2d 100644 --- a/src/Mod/BIM/importers/exportIFC.py +++ b/src/Mod/BIM/importers/exportIFC.py @@ -1289,125 +1289,11 @@ def export(exportList, filename, colors=None, preferences=None): annos = {} if preferences['EXPORT_2D']: + global curvestyles curvestyles = {} if annotations and preferences['DEBUG']: print("exporting 2D objects...") for anno in annotations: - 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) - if anno.isDerivedFrom("Part::Feature"): - if Draft.getType(anno) == "Hatch": - objectType = "HATCH" - elif getattr(anno.ViewObject,"EndArrow",False): - objectType = "LEADER" - elif anno.Shape.Faces: - objectType = "AREA" - else: - objectType = "LINEWORK" - reps = [] - sh = anno.Shape.copy() - sh.scale(preferences['SCALE_FACTOR']) # to meters - ehc = [] - curves = [] - for w in sh.Wires: - curves.append(createCurve(ifcfile,w)) - for e in w.Edges: - ehc.append(e.hashCode()) - if curves: - reps.append(ifcfile.createIfcGeometricCurveSet(curves)) - curves = [] - for e in sh.Edges: - if e.hashCode not in ehc: - curves.append(createCurve(ifcfile,e)) - if curves: - reps.append(ifcfile.createIfcGeometricCurveSet(curves)) - 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)) - tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) - 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) - alg = "LEFT" - if FreeCAD.GuiUp and hasattr(anno.ViewObject,"Justification"): - if anno.ViewObject.Justification == "Right": - alg = "RIGHT" - s = ";".join(anno.Text) - txt = ifcfile.createIfcTextLiteral(s,tpl,alg) - reps = [txt] - elif Draft.getType(anno) in ["Dimension","LinearDimension","AngularDimension"]: - if FreeCAD.GuiUp: - objectType = "DIMENSION" - vp = anno.ViewObject.Proxy - reps = [] - sh = Part.makePolygon([vp.p1,vp.p2,vp.p3,vp.p4]) - sh.scale(preferences['SCALE_FACTOR']) # to meters - ehc = [] - curves = [] - for w in sh.Wires: - curves.append(createCurve(ifcfile,w)) - for e in w.Edges: - ehc.append(e.hashCode()) - if curves: - reps.append(ifcfile.createIfcGeometricCurveSet(curves)) - curves = [] - for e in sh.Edges: - if e.hashCode not in ehc: - curves.append(createCurve(ifcfile,e)) - if curves: - reps.append(ifcfile.createIfcGeometricCurveSet(curves)) - l = FreeCAD.Vector(vp.tbase).multiply(preferences['SCALE_FACTOR']) - zdir = None - xdir = None - if hasattr(vp,"trot"): - r = FreeCAD.Rotation(vp.trot[0],vp.trot[1],vp.trot[2],vp.trot[3]) - zdir = ifcbin.createIfcDirection(tuple(r.multVec(FreeCAD.Vector(0,0,1)))) - xdir = ifcbin.createIfcDirection(tuple(r.multVec(FreeCAD.Vector(1,0,0)))) - pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) - tpl = ifcbin.createIfcAxis2Placement3D(pos,zdir,xdir) - txt = ifcfile.createIfcTextLiteral(vp.string,tpl,"LEFT") - reps.append(txt) - else: - print("Unable to handle object",anno.Label) - continue - - for coldef in ["LineColor","TextColor","ShapeColor"]: - if hasattr(obj.ViewObject,coldef): - rgb = getattr(obj.ViewObject,coldef)[:3] - if rgb in curvestyles: - psa = curvestyles[rgb] - else: - col = ifcbin.createIfcColourRgb(rgb[0],rgb[1],rgb[2]) - cvf = ifcfile.createIfcDraughtingPredefinedCurveFont("continuous") - ics = ifcfile.createIfcCurveStyle('Line',cvf,None,col) - psa = ifcfile.createIfcPresentationStyleAssignment([ics]) - curvestyles[rgb] = psa - for rep in reps: - isi = ifcfile.createIfcStyledItem(rep,[psa],None) - break - - shp = ifcfile.createIfcShapeRepresentation(context,'Annotation','Annotation2D',reps) - rep = ifcfile.createIfcProductDefinitionShape(None,None,[shp]) - l = anno.Label - ann = ifcfile.createIfcAnnotation( - ifcopenshell.guid.new(), - history,l, - '', - objectType, - placement, - rep - ) + ann = create_annotation(anno, ifcfile, context, history, preferences) annos[anno.Name] = ann # groups @@ -2550,3 +2436,128 @@ def writeJson(filename,ifcfile): #print("json:",s) f.write(s) f.close() + + +def create_annotation(anno, ifcfile, context, history, preferences): + """Creates an annotation object""" + + # 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) + if anno.isDerivedFrom("Part::Feature"): + if Draft.getType(anno) == "Hatch": + objectType = "HATCH" + elif getattr(anno.ViewObject,"EndArrow",False): + objectType = "LEADER" + elif anno.Shape.Faces: + objectType = "AREA" + else: + objectType = "LINEWORK" + reps = [] + sh = anno.Shape.copy() + sh.scale(preferences['SCALE_FACTOR']) # to meters + ehc = [] + curves = [] + for w in sh.Wires: + curves.append(createCurve(ifcfile,w)) + for e in w.Edges: + ehc.append(e.hashCode()) + if curves: + reps.append(ifcfile.createIfcGeometricCurveSet(curves)) + curves = [] + for e in sh.Edges: + if e.hashCode not in ehc: + curves.append(createCurve(ifcfile,e)) + if curves: + reps.append(ifcfile.createIfcGeometricCurveSet(curves)) + 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)) + tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) + 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) + alg = "LEFT" + if FreeCAD.GuiUp and hasattr(anno.ViewObject,"Justification"): + if anno.ViewObject.Justification == "Right": + alg = "RIGHT" + s = ";".join(anno.Text) + txt = ifcfile.createIfcTextLiteral(s,tpl,alg) + reps = [txt] + elif Draft.getType(anno) in ["Dimension","LinearDimension","AngularDimension"]: + if FreeCAD.GuiUp: + objectType = "DIMENSION" + vp = anno.ViewObject.Proxy + reps = [] + sh = Part.makePolygon([vp.p1,vp.p2,vp.p3,vp.p4]) + sh.scale(preferences['SCALE_FACTOR']) # to meters + ehc = [] + curves = [] + for w in sh.Wires: + curves.append(createCurve(ifcfile,w)) + for e in w.Edges: + ehc.append(e.hashCode()) + if curves: + reps.append(ifcfile.createIfcGeometricCurveSet(curves)) + curves = [] + for e in sh.Edges: + if e.hashCode not in ehc: + curves.append(createCurve(ifcfile,e)) + if curves: + reps.append(ifcfile.createIfcGeometricCurveSet(curves)) + l = FreeCAD.Vector(vp.tbase).multiply(preferences['SCALE_FACTOR']) + zdir = None + xdir = None + if hasattr(vp,"trot"): + r = FreeCAD.Rotation(vp.trot[0],vp.trot[1],vp.trot[2],vp.trot[3]) + zdir = ifcbin.createIfcDirection(tuple(r.multVec(FreeCAD.Vector(0,0,1)))) + xdir = ifcbin.createIfcDirection(tuple(r.multVec(FreeCAD.Vector(1,0,0)))) + pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) + tpl = ifcbin.createIfcAxis2Placement3D(pos,zdir,xdir) + txt = ifcfile.createIfcTextLiteral(vp.string,tpl,"LEFT") + reps.append(txt) + else: + print("Unable to handle object",anno.Label) + return None + + for coldef in ["LineColor","TextColor","ShapeColor"]: + if hasattr(anno.ViewObject,coldef): + rgb = getattr(anno.ViewObject,coldef)[:3] + if rgb in curvestyles: + psa = curvestyles[rgb] + else: + col = ifcbin.createIfcColourRgb(rgb[0],rgb[1],rgb[2]) + cvf = ifcfile.createIfcDraughtingPredefinedCurveFont("continuous") + ics = ifcfile.createIfcCurveStyle('Line',cvf,None,col) + psa = ifcfile.createIfcPresentationStyleAssignment([ics]) + curvestyles[rgb] = psa + for rep in reps: + isi = ifcfile.createIfcStyledItem(rep,[psa],None) + break + + shp = ifcfile.createIfcShapeRepresentation(context,'Annotation','Annotation2D',reps) + rep = ifcfile.createIfcProductDefinitionShape(None,None,[shp]) + label = anno.Label + description = getattr(anno, "Description", "") + ann = ifcfile.createIfcAnnotation( + ifcopenshell.guid.new(), + history, + label, + description, + objectType, + placement, + rep + ) + return ann diff --git a/src/Mod/BIM/nativeifc/ifc_export.py b/src/Mod/BIM/nativeifc/ifc_export.py new file mode 100644 index 0000000000..9fda8b7735 --- /dev/null +++ b/src/Mod/BIM/nativeifc/ifc_export.py @@ -0,0 +1,146 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2024 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 * +# * * +# *************************************************************************** + +import FreeCAD +import Draft +import ifcopenshell + +from importers import exportIFC +from importers import exportIFCHelper + +from nativeifc import ifc_tools + + +def get_export_preferences(ifcfile): + """returns a preferences dict for exportIFC""" + + prefs = exportIFC.getPreferences() + prefs["SCHEMA"] = ifcfile.wrapped_data.schema_name() + s = ifcopenshell.util.unit.calculate_unit_scale(ifcfile) + # the above lines yields meter -> file unit scale factor. We need mm + prefs["SCALE_FACTOR"] = 0.001 / s + context = ifcfile[ + ifc_tools.get_body_context_ids(ifcfile)[0] + ] # we take the first one (first found subcontext) + return prefs, context + + +def create_product(obj, parent, ifcfile, ifcclass=None): + """Creates an IFC product out of a FreeCAD object""" + + name = obj.Label + description = getattr(obj, "Description", None) + if not ifcclass: + ifcclass = ifc_tools.get_ifctype(obj) + representation, placement = create_representation(obj, ifcfile) + product = ifc_tools.api_run("root.create_entity", ifcfile, ifc_class=ifcclass, name=name) + ifc_tools.set_attribute(ifcfile, product, "Description", description) + ifc_tools.set_attribute(ifcfile, product, "ObjectPlacement", placement) + # TODO below cannot be used at the moment because the ArchIFC exporter returns an + # IfcProductDefinitionShape already and not an IfcShapeRepresentation + # ifc_tools.api_run("geometry.assign_representation", ifcfile, product=product, representation=representation) + ifc_tools.set_attribute(ifcfile, product, "Representation", representation) + # TODO treat subtractions/additions + return product + + +def create_representation(obj, ifcfile): + """Creates a geometry representation for the given object""" + + # TEMPORARY use the Arch exporter + # TODO this is temporary. We should rely on ifcopenshell for this with: + # https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/root/create_entity/index.html + # a new FreeCAD 'engine' should be added to: + # https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/geometry/index.html + # that should contain all typical use cases one could have to convert FreeCAD geometry + # to IFC. + + # setup exporter - TODO do that in the module init + exportIFC.clones = {} + exportIFC.profiledefs = {} + exportIFC.surfstyles = {} + exportIFC.shapedefs = {} + exportIFC.ifcopenshell = ifcopenshell + exportIFC.ifcbin = exportIFCHelper.recycler(ifcfile, template=False) + prefs, context = get_export_preferences(ifcfile) + representation, placement, shapetype = exportIFC.getRepresentation( + ifcfile, context, obj, preferences=prefs + ) + return representation, placement + + +def is_annotation(obj): + """Determines if the given FreeCAD object should be saved as an IfcAnnotation""" + + if getattr(obj, "IfcClass", None) == "IfcAnnotation": + return True + if getattr(obj, "IfcType", None) == "Annotation": + return True + if obj.isDerivedFrom("Part::Part2DObject"): + return True + elif obj.isDerivedFrom("App::Annotation"): + return True + elif Draft.getType(obj) in ["DraftText", + "Text", + "Dimension", + "LinearDimension", + "AngularDimension"]: + return True + elif obj.isDerivedFrom("Part::Feature"): + if obj.Shape and (not obj.Shape.Solids) and obj.Shape.Edges: + if not obj.Shape.Faces: + return True + elif (obj.Shape.BoundBox.XLength < 0.0001) \ + or (obj.Shape.BoundBox.YLength < 0.0001) \ + or (obj.Shape.BoundBox.ZLength < 0.0001): + return True + return False + + +def create_annotation(obj, ifcfile): + """Adds an IfcAnnotation from the given object to the given IFC file""" + + exportIFC.clones = {} + exportIFC.profiledefs = {} + exportIFC.surfstyles = {} + exportIFC.shapedefs = {} + exportIFC.curvestyles = {} + exportIFC.ifcopenshell = ifcopenshell + exportIFC.ifcbin = exportIFCHelper.recycler(ifcfile, template=False) + prefs, context = get_export_preferences(ifcfile) + history = get_history(ifcfile) + # TODO The following prints each edge as a separate IfcGeometricCurveSet + # It should be refined to create polylines instead + anno = exportIFC.create_annotation(obj, ifcfile, context, history, prefs) + return anno + + +def get_history(ifcfile): + """Returns the owner history or None""" + + history = ifcfile.by_type("IfcOwnerHistory") + if history: + history = history[0] + else: + # IFC4 allows to not write any history + history = None + return history diff --git a/src/Mod/BIM/nativeifc/ifc_generator.py b/src/Mod/BIM/nativeifc/ifc_generator.py index be7674519e..070fcc6b75 100644 --- a/src/Mod/BIM/nativeifc/ifc_generator.py +++ b/src/Mod/BIM/nativeifc/ifc_generator.py @@ -26,12 +26,14 @@ used by the execute() method of ifc_objects""" import time +import re import FreeCAD from FreeCAD import Base import Part import ifcopenshell from ifcopenshell.util import element from nativeifc import ifc_tools +from nativeifc import ifc_export import multiprocessing import FreeCADGui from pivy import coin @@ -57,13 +59,32 @@ def generate_geometry(obj, cached=False): return colors = None - # workaround for Group property bug: Avoid having a null shape, otherwise - # a default representation will be created from the object's Group contents - # obj.Shape = Part.makeBox(1, 1, 1) - # fixed in FreeCAD 0.22 - uncomment the line above for earlier versions + ifcfile = ifc_tools.get_ifcfile(obj) + + # annotations + if ifc_export.is_annotation(obj): + element = ifc_tools.get_ifc_element(obj) + if not element: + return + if obj.ShapeMode == "Shape": + shape, placement = get_annotation_shape(element, ifcfile) + if shape: + obj.Shape = shape + if placement: + obj.Placement = placement + elif obj.ViewObject and obj.ShapeMode == "Coin": + node, placement = get_annotation_shape(element, ifcfile, coin=True) + if node: + set_representation(obj.ViewObject, node) + colors = node[0] + else: + set_representation(obj.ViewObject, None) + print_debug(obj) + if placement: + obj.Placement = placement + return # generate the shape or coin node - ifcfile = ifc_tools.get_ifcfile(obj) elements = get_decomposition(obj) if obj.ShapeMode == "Shape": shape, colors = generate_shape(ifcfile, elements, cached) @@ -77,6 +98,7 @@ def generate_geometry(obj, cached=False): elif obj.ViewObject and obj.ShapeMode == "Coin": node, placement = generate_coin(ifcfile, elements, cached) if node: + # TODO this still needs to be fixed #QtCore.QTimer.singleShot(0, lambda: set_representation(obj.ViewObject, node)) set_representation(obj.ViewObject, node) colors = node[0] @@ -339,7 +361,6 @@ def filter_types(elements, obj_ids=[]): elements = [e for e in elements if not e.is_a("IfcOpeningElement")] elements = [e for e in elements if not e.is_a("IfcSpace")] elements = [e for e in elements if not e.is_a("IfcFurnishingElement")] - elements = [e for e in elements if not e.is_a("IfcAnnotation")] elements = [e for e in elements if not e.id() in obj_ids] return elements @@ -451,11 +472,12 @@ def set_representation(vobj, node): coords.point.deleteValues(0) if not node: return - if node[1] and node[2] and node[3] and node[4]: + if node[1] and node[3]: coords.point.setValues(node[1]) - fset.coordIndex.setValues(node[2]) - fset.partIndex.setValues(node[4]) eset.coordIndex.setValues(node[3]) + if node[2] and node[4]: + fset.coordIndex.setValues(node[2]) + fset.partIndex.setValues(node[4]) def print_debug(obj): @@ -524,3 +546,45 @@ def delete_ghost(document): sg = FreeCADGui.getDocument(document.Name).ActiveView.getSceneGraph() sg.removeChild(document.Proxy.ghost) del document.Proxy.ghost + + +def get_annotation_shape(annotation, ifcfile, coin=False): + """Returns a shape or a coin node form an IFC annotation""" + + import Part + from importers import importIFCHelper + + shape = None + placement = None + ifcscale = importIFCHelper.getScaling(ifcfile) + shapes2d = [] + for rep in annotation.Representation.Representations: + if rep.RepresentationIdentifier in ["Annotation", "FootPrint", "Axis"]: + sh = importIFCHelper.get2DShape(rep, ifcscale) + if sh: + shapes2d.extend(sh) + if shapes2d: + shape = Part.makeCompound(shapes2d) + placement = importIFCHelper.getPlacement(annotation.ObjectPlacement, ifcscale) + if coin: + iv = shape.writeInventor() + iv = iv.replace("\n", "") + segs = re.findall(r"point \[.*?\]",iv) + segs = [s.replace("point [","").replace("]","").strip() for s in segs] + segs = [s.split(" ") for s in segs] + verts = [] + edges = [] + for pair in segs: + v1 = tuple([float(v) for v in pair[0].split()]) + v2 = tuple([float(v) for v in pair[1].split()]) + if not v1 in verts: + verts.append(v1) + edges.append(verts.index(v1)) + if not v2 in verts: + verts.append(v2) + edges.append(verts.index(v2)) + edges.append(-1) + shape = [[None, verts, [], edges]] + # unify nodes + shape = unify(shape) + return shape, placement diff --git a/src/Mod/BIM/nativeifc/ifc_status.py b/src/Mod/BIM/nativeifc/ifc_status.py index ec2a1f8ce2..72381b8a16 100644 --- a/src/Mod/BIM/nativeifc/ifc_status.py +++ b/src/Mod/BIM/nativeifc/ifc_status.py @@ -355,6 +355,7 @@ def lock_document(): from nativeifc import ifc_tools # lazy loading from importers import exportIFC from nativeifc import ifc_geometry + from nativeifc import ifc_export from PySide import QtCore doc = FreeCAD.ActiveDocument @@ -379,7 +380,7 @@ def lock_document(): if rest: # 1b some objects are outside objs = find_toplevel(rest) - prefs, context = ifc_tools.get_export_preferences(ifcfile) + prefs, context = ifc_export.get_export_preferences(ifcfile) products = exportIFC.export(objs, ifcfile, preferences=prefs) for product in products.values(): if not getattr(product, "ContainedInStructure", None): @@ -416,7 +417,7 @@ def lock_document(): ifc_tools.convert_document(doc, silent=True) ifcfile = doc.Proxy.ifcfile objs = find_toplevel(doc.Objects) - prefs, context = ifc_tools.get_export_preferences(ifcfile) + prefs, context = ifc_export.get_export_preferences(ifcfile) exportIFC.export(objs, ifcfile, preferences=prefs) for n in [o.Name for o in doc.Objects]: if doc.getObject(n): diff --git a/src/Mod/BIM/nativeifc/ifc_tools.py b/src/Mod/BIM/nativeifc/ifc_tools.py index df157bfb2f..fcde65740a 100644 --- a/src/Mod/BIM/nativeifc/ifc_tools.py +++ b/src/Mod/BIM/nativeifc/ifc_tools.py @@ -29,8 +29,6 @@ import os import FreeCAD import Draft import Arch -from importers import exportIFC -from importers import exportIFCHelper import ifcopenshell from ifcopenshell import geom @@ -47,6 +45,7 @@ from nativeifc import ifc_viewproviders from nativeifc import ifc_import from nativeifc import ifc_layers from nativeifc import ifc_status +from nativeifc import ifc_export SCALE = 1000.0 # IfcOpenShell works in meters, FreeCAD works in mm SHORT = False # If True, only Step ID attribute is created @@ -739,8 +738,6 @@ def filter_elements(elements, ifcfile, expand=True, spaces=False, assemblies=Tru elements = [e for e in elements if not e.is_a("IfcProject")] # skip furniture for now, they can be lazy loaded probably elements = [e for e in elements if not e.is_a("IfcFurnishingElement")] - # skip annotations for now - elements = [e for e in elements if not e.is_a("IfcAnnotation")] return elements @@ -1011,7 +1008,10 @@ def aggregate(obj, parent, mode=None): ifcclass = None if mode == "opening": ifcclass = "IfcOpeningElement" - product = create_product(obj, parent, ifcfile, ifcclass) + if ifc_export.is_annotation(obj): + product = ifc_export.create_annotation(obj, ifcfile) + else: + product = ifc_export.create_product(obj, parent, ifcfile, ifcclass) shapemode = getattr(parent, "ShapeMode", DEFAULT_SHAPEMODE) newobj = create_object(product, obj.Document, ifcfile, shapemode) new = True @@ -1065,69 +1065,6 @@ def deaggregate(obj, parent): parent.Proxy.removeObject(parent, obj) -def create_product(obj, parent, ifcfile, ifcclass=None): - """Creates an IFC product out of a FreeCAD object""" - - name = obj.Label - description = getattr(obj, "Description", None) - if not ifcclass: - ifcclass = get_ifctype(obj) - representation, placement, shapetype = create_representation(obj, ifcfile) - product = api_run("root.create_entity", ifcfile, ifc_class=ifcclass, name=name) - set_attribute(ifcfile, product, "Description", description) - set_attribute(ifcfile, product, "ObjectPlacement", placement) - # TODO below cannot be used at the moment because the ArchIFC exporter returns an - # IfcProductDefinitionShape already and not an IfcShapeRepresentation - # api_run("geometry.assign_representation", ifcfile, product=product, representation=representation) - set_attribute(ifcfile, product, "Representation", representation) - # additions - if hasattr(obj,"Additions") and shapetype in ["extrusion","no shape"]: - for addobj in obj.Additions: - r2,p2,c2 = create_representation(addobj, ifcfile) - cl2 = get_ifctype(addobj) - addprod = api_run("root.create_entity", ifcfile, ifc_class=cl2, name=addobj.Label) - set_attribute(ifcfile, addprod, "Description", getattr(addobj, "Description", "")) - set_attribute(ifcfile, addprod, "ObjectPlacement", p2) - set_attribute(ifcfile, addprod, "Representation", r2) - create_relationship(None, addobj, product, addprod, ifcfile) - # subtractions - if hasattr(obj,"Subtractions") and shapetype in ["extrusion","no shape"]: - for subobj in obj.Subtractions: - r3,p3,c3 = create_representation(subobj, ifcfile) - cl3 = "IfcOpeningElement" - subprod = api_run("root.create_entity", ifcfile, ifc_class=cl3, name=subobj.Label) - set_attribute(ifcfile, subprod, "Description", getattr(subobj, "Description", "")) - set_attribute(ifcfile, subprod, "ObjectPlacement", p3) - set_attribute(ifcfile, subprod, "Representation", r3) - create_relationship(None, subobj, product, subprod, ifcfile) - return product - - -def create_representation(obj, ifcfile): - """Creates a geometry representation for the given object""" - - # TEMPORARY use the Arch exporter - # TODO this is temporary. We should rely on ifcopenshell for this with: - # https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/root/create_entity/index.html - # a new FreeCAD 'engine' should be added to: - # https://blenderbim.org/docs-python/autoapi/ifcopenshell/api/geometry/index.html - # that should contain all typical use cases one could have to convert FreeCAD geometry - # to IFC. - - # setup exporter - TODO do that in the module init - exportIFC.clones = {} - exportIFC.profiledefs = {} - exportIFC.surfstyles = {} - exportIFC.shapedefs = {} - exportIFC.ifcopenshell = ifcopenshell - exportIFC.ifcbin = exportIFCHelper.recycler(ifcfile, template=False) - prefs, context = get_export_preferences(ifcfile) - representation, placement, shapetype = exportIFC.getRepresentation( - ifcfile, context, obj, preferences=prefs - ) - return representation, placement, shapetype - - def get_ifctype(obj): """Returns a valid IFC type from an object""" @@ -1144,20 +1081,6 @@ def get_ifctype(obj): return "IfcBuildingElementProxy" -def get_export_preferences(ifcfile): - """returns a preferences dict for exportIFC""" - - prefs = exportIFC.getPreferences() - prefs["SCHEMA"] = ifcfile.wrapped_data.schema_name() - s = ifcopenshell.util.unit.calculate_unit_scale(ifcfile) - # the above lines yields meter -> file unit scale factor. We need mm - prefs["SCALE_FACTOR"] = 0.001 / s - context = ifcfile[ - get_body_context_ids(ifcfile)[0] - ] # we take the first one (first found subcontext) - return prefs, context - - def get_subvolume(obj): """returns a subface + subvolume from a window object""" @@ -1240,7 +1163,7 @@ def create_relationship(old_obj, obj, parent, element, ifcfile, mode=None): if old_obj: tempface, tempobj = get_subvolume(old_obj) if tempobj: - opening = create_product(tempobj, parent, ifcfile, "IfcOpeningElement") + opening = ifc_export.create_product(tempobj, parent, ifcfile, "IfcOpeningElement") set_attribute(ifcfile, product, "Name", "Opening") old_obj.Document.removeObject(tempobj.Name) if tempface: