From 19744a9f8330a79fd15b89b16eb54efd8b22109b Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Fri, 10 Apr 2020 14:31:14 +0200 Subject: [PATCH] Arch: Export ortho arrays to IFC --- src/Mod/Arch/exportIFC.py | 242 +++++++++++++++++++++++++++----------- 1 file changed, 176 insertions(+), 66 deletions(-) diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index f0b3c71cbe..596b137331 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -137,7 +137,8 @@ def getPreferences(): 'ADD_DEFAULT_STOREY': p.GetBool("IfcAddDefaultStorey",False), 'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding",True), 'IFC_UNIT': u, - 'SCALE_FACTOR': f + 'SCALE_FACTOR': f, + 'GET_STANDARD': p.GetBool("getStandardType",False) } return preferences @@ -162,6 +163,8 @@ def export(exportList,filename,colors=None,preferences=None): FreeCAD.Console.PrintMessage("Visit https://www.freecadweb.org/wiki/Arch_IFC to learn how to install it\n") return + # process template + version = FreeCAD.Version() owner = FreeCAD.ActiveDocument.CreatedBy email = '' @@ -171,11 +174,10 @@ def export(exportList,filename,colors=None,preferences=None): email = s[1].strip(">") global template template = ifctemplate.replace("$version",version[0]+"."+version[1]+" build "+version[2]) - getstd = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("getStandardType",False) if hasattr(ifcopenshell,"schema_identifier"): schema = ifcopenshell.schema_identifier elif hasattr(ifcopenshell,"version") and (float(ifcopenshell.version[:3]) >= 0.6): - # v0.6 allows to set our own schema + # v0.6 onwards allows to set our own schema schema = ["IFC4", "IFC2X3"][FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)] else: schema = "IFC2X3" @@ -196,6 +198,9 @@ def export(exportList,filename,colors=None,preferences=None): of.write(template) of.close() os.close(templatefilehandle) + + # create IFC file + global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs ifcfile = ifcopenshell.open(templatefile) ifcfile = exportIFCHelper.writeUnits(ifcfile,preferences["IFC_UNIT"]) @@ -211,13 +216,17 @@ def export(exportList,filename,colors=None,preferences=None): if obj.Shape: if obj.Shape.Edges and (not obj.Shape.Faces): annotations.append(obj) + # clean objects list of unwanted types + objectslist = [obj for obj in objectslist if obj not in annotations] objectslist = Arch.pruneIncluded(objectslist,strict=True) objectslist = [obj for obj in objectslist if Draft.getType(obj) not in ["Dimension","Material","MaterialContainer","WorkingPlaneProxy"]] if preferences['FULL_PARAMETRIC']: objectslist = Arch.getAllChildren(objectslist) + # create project and context + contextCreator = exportIFCHelper.ContextCreator(ifcfile, objectslist) context = contextCreator.model_view_subcontext project = contextCreator.project @@ -227,6 +236,8 @@ def export(exportList,filename,colors=None,preferences=None): decl = Draft.getObjectsOfType(objectslist, "Site")[0].Declination.getValueAs(FreeCAD.Units.Radian) contextCreator.model_context.TrueNorth.DirectionRatios = (math.cos(decl+math.pi/2), math.sin(decl+math.pi/2)) + # define holders for the different types we create + products = {} # { Name: IfcEntity, ... } subproducts = {} # { Name: IfcEntity, ... } for storing additions/subtractions and other types of subcomponents of a product surfstyles = {} # { (r,g,b): IfcEntity, ... } @@ -267,33 +278,57 @@ def export(exportList,filename,colors=None,preferences=None): # getting generic data - name = obj.Label - if six.PY2: - name = name.encode("utf8") - description = obj.Description if hasattr(obj,"Description") else "" - if six.PY2: - description = description.encode("utf8") - - # getting uid - - uid = None - if hasattr(obj,"IfcData"): - if "IfcUID" in obj.IfcData.keys(): - uid = str(obj.IfcData["IfcUID"]) - if not uid: - uid = ifcopenshell.guid.new() - # storing the uid for further use - if preferences['STORE_UID'] and hasattr(obj,"IfcData"): - d = obj.IfcData - d["IfcUID"] = uid - obj.IfcData = d - + name = getText("Name",obj) + description = getText("Description",obj) + uid = getUID(obj,preferences) ifctype = getIfcTypeFromObj(obj) if ifctype == "IfcGroup": groups[obj.Name] = [o.Name for o in obj.Group] continue + # handle assemblies (arrays, app::parts, references, etc...) + + assemblyElements = [] + + if ifctype == "IfcArray": + if obj.ArrayType == "ortho": + clonedeltas = [] + for i in range(obj.NumberX): + clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)) + for j in range(obj.NumberY): + if j > 0: + clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)+(j*obj.IntervalY)) + for k in range(obj.NumberZ): + if k > 0: + clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)+(j*obj.IntervalY)+(k*obj.IntervalZ)) + #print("clonedeltas:",clonedeltas) + for delta in clonedeltas: + representation,placement,shapetype = getRepresentation( + ifcfile, + context, + obj.Base, + forcebrep=(getBrepFlag(obj.Base,preferences)), + colors=colors, + preferences=preferences, + forceclone=delta + ) + subproduct = createProduct( + ifcfile, + obj.Base, + getIfcTypeFromObj(obj.Base), + getUID(obj.Base,preferences), + history, + getText("Name",obj.Base), + getText("Description",obj.Base), + placement, + representation, + preferences, + schema) + + assemblyElements.append(subproduct) + ifctype = "IfcElementAssembly" + # export grids if ifctype in ["IfcAxis","IfcAxisSystem","IfcGrid"]: @@ -356,61 +391,55 @@ def export(exportList,filename,colors=None,preferences=None): if ifctype not in ArchIFCSchema.IfcProducts.keys(): ifctype = "IfcBuildingElementProxy" - # getting the "Force BREP" flag - - brepflag = False - if hasattr(obj,"IfcData"): - if "FlagForceBrep" in obj.IfcData.keys(): - if obj.IfcData["FlagForceBrep"] == "True": - brepflag = True - # getting the representation representation,placement,shapetype = getRepresentation( ifcfile, context, obj, - forcebrep=(brepflag or preferences['FORCE_BREP']), + forcebrep=(getBrepFlag(obj,preferences)), colors=colors, preferences=preferences ) - if getstd: + if preferences['GET_STANDARD']: if isStandardCase(obj,ifctype): ifctype += "StandardCase" - if preferences['DEBUG']: print(str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name) - - # setting the arguments - - kwargs = { - "GlobalId": uid, - "OwnerHistory": history, - "Name": name, - "Description": description, - "ObjectPlacement": placement, - "Representation": representation - } - if ifctype == "IfcSite": - kwargs.update({ - "RefLatitude":dd2dms(obj.Latitude), - "RefLongitude":dd2dms(obj.Longitude), - "RefElevation":obj.Elevation.Value*preferences['SCALE_FACTOR'], - "SiteAddress":buildAddress(obj,ifcfile), - "CompositionType": "ELEMENT" - }) - if schema == "IFC2X3": - kwargs = exportIFC2X3Attributes(obj, kwargs, preferences['SCALE_FACTOR']) - else: - kwargs = exportIfcAttributes(obj, kwargs, preferences['SCALE_FACTOR']) + if preferences['DEBUG']: + print(str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name) # creating the product - #print(obj.Label," : ",ifctype," : ",kwargs) - product = getattr(ifcfile,"create"+ifctype)(**kwargs) + product = createProduct( + ifcfile, + obj, + ifctype, + uid, + history, + name, + description, + placement, + representation, + preferences, + schema) + products[obj.Name] = product if ifctype in ["IfcBuilding","IfcBuildingStorey","IfcSite","IfcSpace"]: spatialelements[obj.Name] = product + # gather assembly subelements + + if assemblyElements: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'Assembly', + '', + products[obj.Name], + assemblyElements + ) + if preferences['DEBUG']: print(" aggregating",len(assemblyElements),"object(s)") + # additions if hasattr(obj,"Additions") and (shapetype in ["extrusion","no shape"]): @@ -1743,9 +1772,10 @@ def getProfile(ifcfile,p): return profile -def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None,preferences=None): +def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None,preferences=None,forceclone=False): - """returns an IfcShapeRepresentation object or None""" + """returns an IfcShapeRepresentation object or None. forceclone can be False (does nothing), + "store" or True (stores the object as clone base) or a Vector (creates a clone)""" import Part import DraftGeomUtils @@ -1756,20 +1786,27 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess shapetype = "no shape" tostore = False subplacement = None + skipshape = False # check for clones - if (not subtraction) and (not forcebrep): + if ((not subtraction) and (not forcebrep)) or forceclone: + if forceclone: + if not obj.Name in clones: + clones[obj.Name] = [] for k,v in clones.items(): if (obj.Name == k) or (obj.Name in v): if k in sharedobjects: # base shape already exists repmap = sharedobjects[k] pla = obj.getGlobalPlacement() + pos = FreeCAD.Vector(pla.Base) + if isinstance(forceclone,FreeCAD.Vector): + pos += forceclone axis1 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0)))) axis2 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0)))) axis3 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1)))) - origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(preferences['SCALE_FACTOR']))) + origin = ifcbin.createIfcCartesianPoint(tuple(pos.multiply(preferences['SCALE_FACTOR']))) transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3) mapitem = ifcfile.createIfcMappedItem(repmap,transf) shapes = [mapitem] @@ -1783,7 +1820,11 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess if obj.isDerivedFrom("Part::Feature") and (len(obj.Shape.Solids) > 1) and hasattr(obj,"Axis") and obj.Axis: forcebrep = True - if (not shapes) and (not forcebrep): + # specific cases that must ignore their own shape + if Draft.getType(obj) in ["Array"]: + skipshape = True + + if (not shapes) and (not forcebrep) and (not skipshape): profile = None ev = FreeCAD.Vector() if hasattr(obj,"Proxy"): @@ -1858,7 +1899,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess solidType = "SweptSolid" shapetype = "extrusion" - if not shapes: + if (not shapes) and (not skipshape): # check if we keep a null shape (additions-only object) @@ -2106,3 +2147,72 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess productdef = ifcfile.createIfcProductDefinitionShape(None,None,[representation]) return productdef,placement,shapetype + + +def getBrepFlag(obj,preferences): + """returns True if the object must be exported as BREP""" + brepflag = False + if preferences['FORCE_BREP']: + return True + if hasattr(obj,"IfcData"): + if "FlagForceBrep" in obj.IfcData.keys(): + if obj.IfcData["FlagForceBrep"] == "True": + brepflag = True + return brepflag + + +def createProduct(ifcfile,obj,ifctype,uid,history,name,description,placement,representation,preferences,schema): + """creates a product in the given IFC file""" + + kwargs = { + "GlobalId": uid, + "OwnerHistory": history, + "Name": name, + "Description": description, + "ObjectPlacement": placement, + "Representation": representation + } + if ifctype == "IfcSite": + kwargs.update({ + "RefLatitude":dd2dms(obj.Latitude), + "RefLongitude":dd2dms(obj.Longitude), + "RefElevation":obj.Elevation.Value*preferences['SCALE_FACTOR'], + "SiteAddress":buildAddress(obj,ifcfile), + "CompositionType": "ELEMENT" + }) + if schema == "IFC2X3": + kwargs = exportIFC2X3Attributes(obj, kwargs, preferences['SCALE_FACTOR']) + else: + kwargs = exportIfcAttributes(obj, kwargs, preferences['SCALE_FACTOR']) + product = getattr(ifcfile,"create"+ifctype)(**kwargs) + return product + + +def getUID(obj,preferences): + """gets or creates an UUID for an object""" + + uid = None + if hasattr(obj,"IfcData"): + if "IfcUID" in obj.IfcData.keys(): + uid = str(obj.IfcData["IfcUID"]) + if not uid: + uid = ifcopenshell.guid.new() + # storing the uid for further use + if preferences['STORE_UID'] and hasattr(obj,"IfcData"): + d = obj.IfcData + d["IfcUID"] = uid + obj.IfcData = d + return uid + + +def getText(field,obj): + """Returns the value of a text property of an object""" + + result = "" + if field == "Name": + field = "Label" + if hasattr(obj,field): + result = getattr(obj,field) + if six.PY2: + result = result.encode("utf8") + return result