diff --git a/src/Mod/Arch/CMakeLists.txt b/src/Mod/Arch/CMakeLists.txt index 753a0416c1..cd9b753cf6 100644 --- a/src/Mod/Arch/CMakeLists.txt +++ b/src/Mod/Arch/CMakeLists.txt @@ -46,6 +46,7 @@ SET(Arch_SRCS ArchReference.py ArchFence.py OfflineRenderingUtils.py + exportIFC.py ) SET(Dice3DS_SRCS diff --git a/src/Mod/Arch/Init.py b/src/Mod/Arch/Init.py index 10fc0bd968..f139150f3e 100644 --- a/src/Mod/Arch/Init.py +++ b/src/Mod/Arch/Init.py @@ -23,7 +23,7 @@ # add import/export types FreeCAD.addImportType("Industry Foundation Classes (*.ifc)","importIFC") -FreeCAD.addExportType("Industry Foundation Classes (*.ifc)","importIFC") +FreeCAD.addExportType("Industry Foundation Classes (*.ifc)","exportIFC") FreeCAD.addImportType("Wavefront OBJ - Arch module (*.obj)","importOBJ") FreeCAD.addExportType("Wavefront OBJ - Arch module (*.obj)","importOBJ") FreeCAD.addExportType("WebGL file (*.html)","importWebGL") diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py new file mode 100644 index 0000000000..48be36ec21 --- /dev/null +++ b/src/Mod/Arch/exportIFC.py @@ -0,0 +1,2054 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2014 * +#* Yorik van Havre * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU Lesser General Public License (LGPL) * +#* as published by the Free Software Foundation; either version 2 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 Library 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 * +#* * +#*************************************************************************** + +from __future__ import print_function + +__title__ = "FreeCAD IFC export" +__author__ = "Yorik van Havre","Jonathan Wiedemann","Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +import six +import os +import time +import tempfile +import math + +import FreeCAD +import Part +import Draft +import Arch +import DraftVecUtils +import ArchIFCSchema +from DraftGeomUtils import vec +from importIFC import recycler +from importIFC import dd2dms +from importIFC import decode + + +## @package exportIFC +# \ingroup ARCH +# \brief IFC file format exporter +# +# This module provides tools to export IFC files. + +DEBUG = False # Set to True to see debug messages. Otherwise, totally silent + +if open.__module__ in ['__builtin__','io']: + pyopen = open + # pyopen is use in exporter to open a file in Arch + + +# ************************************************************************************************ +# ********** templates and other definitions **** + +# specific FreeCAD <-> IFC slang translations +translationtable = { + "Foundation":"Footing", + "Floor":"BuildingStorey", + "Rebar":"ReinforcingBar", + "HydroEquipment":"SanitaryTerminal", + "ElectricEquipment":"ElectricAppliance", + "Furniture":"FurnishingElement", + "Stair Flight":"StairFlight", + "Curtain Wall":"CurtainWall", + "Pipe Segment":"PipeSegment", + "Pipe Fitting":"PipeFitting" +} + + +# the base IFC template for export +ifctemplate = """ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('$filename','$timestamp',('$owner','$email'),('$company'),'IfcOpenShell','IfcOpenShell',''); +FILE_SCHEMA(('$ifcschema')); +ENDSEC; +DATA; +#1=IFCPERSON($,$,'$owner',$,$,$,$,$); +#2=IFCORGANIZATION($,'$company',$,$,$); +#3=IFCPERSONANDORGANIZATION(#1,#2,$); +#4=IFCAPPLICATION(#2,'$version','FreeCAD','118df2cf_ed21_438e_a41'); +#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,$now); +#6=IFCDIRECTION((1.,0.,0.)); +#7=IFCDIRECTION((0.,0.,1.)); +#8=IFCCARTESIANPOINT((0.,0.,0.)); +#9=IFCAXIS2PLACEMENT3D(#8,#7,#6); +#10=IFCDIRECTION((0.,1.,0.)); +#11=IFCGEOMETRICREPRESENTATIONCONTEXT('Plan','Model',3,1.E-05,#9,#10); +#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); +#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); +#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); +#19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); +#20=IFCPROJECT('$projectid',#5,'$project',$,$,$,$,(#11),#19); +ENDSEC; +END-ISO-10303-21; +""" + + +# ************************************************************************************************ +# ********** duplicate methods **************** +# TODO red rid of this duplicate + +def getPreferences(): + + """retrieves IFC preferences""" + + global DEBUG, PREFIX_NUMBERS, SKIP, SEPARATE_OPENINGS + global ROOT_ELEMENT, GET_EXTRUSIONS, MERGE_MATERIALS + global MERGE_MODE_ARCH, MERGE_MODE_STRUCT, CREATE_CLONES + global FORCE_BREP, IMPORT_PROPERTIES, STORE_UID, SERIALIZE + global SPLIT_LAYERS, EXPORT_2D, FULL_PARAMETRIC, FITVIEW_ONIMPORT + global ADD_DEFAULT_SITE, ADD_DEFAULT_STOREY, ADD_DEFAULT_BUILDING + p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") + if FreeCAD.GuiUp and p.GetBool("ifcShowDialog",False): + import FreeCADGui + FreeCADGui.showPreferences("Import-Export",0) + DEBUG = p.GetBool("ifcDebug",False) + PREFIX_NUMBERS = p.GetBool("ifcPrefixNumbers",False) + SKIP = p.GetString("ifcSkip","").split(",") + SEPARATE_OPENINGS = p.GetBool("ifcSeparateOpenings",False) + ROOT_ELEMENT = p.GetString("ifcRootElement","IfcProduct") + GET_EXTRUSIONS = p.GetBool("ifcGetExtrusions",False) + MERGE_MATERIALS = p.GetBool("ifcMergeMaterials",False) + MERGE_MODE_ARCH = p.GetInt("ifcImportModeArch",0) + MERGE_MODE_STRUCT = p.GetInt("ifcImportModeStruct",1) + if MERGE_MODE_ARCH > 0: + SEPARATE_OPENINGS = False + GET_EXTRUSIONS = False + if not SEPARATE_OPENINGS: + SKIP.append("IfcOpeningElement") + CREATE_CLONES = p.GetBool("ifcCreateClones",True) + FORCE_BREP = p.GetBool("ifcExportAsBrep",False) + IMPORT_PROPERTIES = p.GetBool("ifcImportProperties",False) + STORE_UID = p.GetBool("ifcStoreUid",True) + SERIALIZE = p.GetBool("ifcSerialize",False) + SPLIT_LAYERS = p.GetBool("ifcSplitLayers",False) + EXPORT_2D = p.GetBool("ifcExport2D",True) + FULL_PARAMETRIC = p.GetBool("IfcExportFreeCADProperties",False) + FITVIEW_ONIMPORT = p.GetBool("ifcFitViewOnImport",False) + ADD_DEFAULT_SITE = p.GetBool("IfcAddDefaultSite",False) + ADD_DEFAULT_STOREY = p.GetBool("IfcAddDefaultStorey",False) + ADD_DEFAULT_BUILDING = p.GetBool("IfcAddDefaultBuilding",True) + + +# ************************************************************************************************ +# ********** export IFC **************** + +def export(exportList,filename,colors=None): + + """export(exportList,filename,colors=None) -- exports FreeCAD contents to an IFC file. + colors is an optional dictionary of objName:shapeColorTuple or objName:diffuseColorList elements + to be used in non-GUI mode if you want to be able to export colors.""" + + getPreferences() + + try: + global ifcopenshell + import ifcopenshell + except: + FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n") + return + + version = FreeCAD.Version() + owner = FreeCAD.ActiveDocument.CreatedBy + email = '' + if ("@" in owner) and ("<" in owner): + s = owner.split("<") + owner = s[0].strip() + 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 + schema = ["IFC4", "IFC2X3"][FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)] + else: + schema = "IFC2X3" + if DEBUG: print("Exporting an",schema,"file...") + template = template.replace("$ifcschema",schema) + template = template.replace("$owner",owner) + template = template.replace("$company",FreeCAD.ActiveDocument.Company) + template = template.replace("$email",email) + template = template.replace("$now",str(int(time.time()))) + template = template.replace("$projectid",FreeCAD.ActiveDocument.Uid[:22].replace("-","_")) + template = template.replace("$project",FreeCAD.ActiveDocument.Name) + template = template.replace("$filename",os.path.basename(filename)) + template = template.replace("$timestamp",str(time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()))) + if hasattr(ifcopenshell,"version"): + template = template.replace("IfcOpenShell","IfcOpenShell "+ifcopenshell.version) + templatefilehandle,templatefile = tempfile.mkstemp(suffix=".ifc") + of = pyopen(templatefile,"w") + if six.PY2: + template = template.encode("utf8") + of.write(template) + of.close() + os.close(templatefilehandle) + global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs + ifcfile = ifcopenshell.open(templatefile) + history = ifcfile.by_type("IfcOwnerHistory")[0] + context = ifcfile.by_type("IfcGeometricRepresentationContext")[0] + project = ifcfile.by_type("IfcProject")[0] + objectslist = Draft.getGroupContents(exportList,walls=True,addgroups=True) + if(Draft.getObjectsOfType(objectslist, "Site")): # we assume one site and one representation context only + trueNorthX = math.tan(-Draft.getObjectsOfType(objectslist, "Site")[0].Declination.getValueAs(FreeCAD.Units.Radian)) + context.TrueNorth.DirectionRatios = (trueNorthX, 1., 1.) + annotations = [] + for obj in objectslist: + if obj.isDerivedFrom("Part::Part2DObject"): + annotations.append(obj) + elif obj.isDerivedFrom("App::Annotation") or (Draft.getType(obj) == "DraftText"): + annotations.append(obj) + elif obj.isDerivedFrom("Part::Feature"): + 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 FULL_PARAMETRIC: + objectslist = Arch.getAllChildren(objectslist) + products = {} # { Name: IfcEntity, ... } + subproducts = {} # { Name: IfcEntity, ... } for storing additions/subtractions and other types of subcomponents of a product + surfstyles = {} # { (r,g,b): IfcEntity, ... } + clones = {} # { Basename:[Clonename1,Clonename2,...] } + sharedobjects = {} # { BaseName: IfcRepresentationMap } + count = 1 + groups = {} # { Host: [Child,Child,...] } + profiledefs = {} # { ProfileDefString:profiledef,...} + shapedefs = {} # { ShapeDefString:[shapes],... } + spatialelements = {} # {Name:IfcEntity, ... } + + # reusable entity system + + global ifcbin + ifcbin = recycler(ifcfile) + + # build clones table + + if CREATE_CLONES: + for o in objectslist: + b = Draft.getCloneBase(o,strict=True) + if b: + clones.setdefault(b.Name,[]).append(o.Name) + + #print("clones table: ",clones) + #print(objectslist) + + # testing if more than one site selected (forbidden in IFC) + + if len(Draft.getObjectsOfType(objectslist,"Site")) > 1: + FreeCAD.Console.PrintError("More than one site is selected, which is forbidden by IFC standards. Please export only one site by IFC file.\n") + return + + # products + + for obj in objectslist: + + # 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 STORE_UID and hasattr(obj,"IfcData"): + d = obj.IfcData + d["IfcUID"] = uid + obj.IfcData = d + + ifctype = getIfcTypeFromObj(obj) + + if ifctype == "IfcGroup": + groups[obj.Name] = [o.Name for o in obj.Group] + continue + + # export grids + + if ifctype in ["IfcAxis","IfcAxisSystem","IfcGrid"]: + ifcaxes = [] + ifcpols = [] + if ifctype == "IfcAxis": + # make sure this axis is not included in something else already + standalone = True + for p in obj.InList: + if hasattr(p,"Axes") and (obj in p.Axes): + if p in objectslist: + axgroups = [] + standalone = False + break + if standalone: + axgroups = [obj.Proxy.getAxisData(obj)] + else: + axgroups = obj.Proxy.getAxisData(obj) + if not axgroups: + continue + ifctype = "IfcGrid" + for axg in axgroups: + ifcaxg = [] + for ax in axg: + p1 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[0]).multiply(0.001))) + p2 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[1]).multiply(0.001))) + pol = ifcbin.createIfcPolyline([p1,p2]) + ifcpols.append(pol) + axis = ifcfile.createIfcGridAxis(ax[2],pol,True) + ifcaxg.append(axis) + if len(ifcaxes) < 3: + ifcaxes.append(ifcaxg) + else: + ifcaxes[2] = ifcaxes[2]+ifcaxg # IfcGrid can have max 3 axes systems + u = None + v = None + w = None + if ifcaxes: + u = ifcaxes[0] + if len(ifcaxes) > 1: + v = ifcaxes[1] + if len(ifcaxes) > 2: + w = ifcaxes[2] + if u and v: + if DEBUG: print(str(count).ljust(3)," : ", ifctype, " (",str(len(ifcpols)),"axes ) : ",name) + 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) + plac = ifcbin.createIfcLocalPlacement(gpl) + cset = ifcfile.createIfcGeometricCurveSet(ifcpols) + #subc = ifcfile.createIfcGeometricRepresentationSubContext('FootPrint','Model',context,None,"MODEL_VIEW",None,None,None,None,None) + srep = ifcfile.createIfcShapeRepresentation(context,'FootPrint',"GeometricCurveSet",ifcpols) + pdef = ifcfile.createIfcProductDefinitionShape(None,None,[srep]) + grid = ifcfile.createIfcGrid(uid,history,name,description,None,plac,pdef,u,v,w) + products[obj.Name] = grid + count += 1 + continue + + 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 FORCE_BREP),colors=colors) + if getstd: + if isStandardCase(obj,ifctype): + ifctype += "StandardCase" + + if 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/1000.0, + "SiteAddress":buildAddress(obj,ifcfile), + "CompositionType": "ELEMENT" + }) + if schema == "IFC2X3": + kwargs = exportIFC2X3Attributes(obj, kwargs) + else: + kwargs = exportIfcAttributes(obj, kwargs) + + # creating the product + + #print(obj.Label," : ",ifctype," : ",kwargs) + product = getattr(ifcfile,"create"+ifctype)(**kwargs) + products[obj.Name] = product + if ifctype in ["IfcBuilding","IfcBuildingStorey","IfcSite","IfcSpace"]: + spatialelements[obj.Name] = product + + # additions + + if hasattr(obj,"Additions") and (shapetype in ["extrusion","no shape"]): + for o in obj.Additions: + r2,p2,c2 = getRepresentation(ifcfile,context,o,colors=colors) + if DEBUG: print(" adding ",c2," : ",o.Label) + l = o.Label + if six.PY2: + l = l.encode("utf8") + prod2 = ifcfile.createIfcBuildingElementProxy( + ifcopenshell.guid.new(), + history, + l, + None, + None, + p2, + r2, + None, + "ELEMENT" + ) + subproducts[o.Name] = prod2 + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'Addition', + '', + product, + [prod2] + ) + + # subtractions + + guests = [] + for o in obj.InList: + if hasattr(o,"Hosts"): + for co in o.Hosts: + if co == obj: + if o not in guests: + guests.append(o) + if hasattr(obj,"Subtractions") and (shapetype in ["extrusion","no shape"]): + for o in obj.Subtractions + guests: + r2,p2,c2 = getRepresentation(ifcfile,context,o,subtraction=True,colors=colors) + if DEBUG: print(" subtracting ",c2," : ",o.Label) + l = o.Label + if six.PY2: + l = l.encode("utf8") + prod2 = ifcfile.createIfcOpeningElement( + ifcopenshell.guid.new(), + history, + l, + None, + None, + p2, + r2, + None + ) + subproducts[o.Name] = prod2 + ifcfile.createIfcRelVoidsElement( + ifcopenshell.guid.new(), + history, + 'Subtraction', + '', + product, + prod2 + ) + + # properties + + ifcprop = False + if hasattr(obj,"IfcProperties"): + + if obj.IfcProperties: + + ifcprop = True + + if isinstance(obj.IfcProperties,dict): + + # IfcProperties is a dictionary (FreeCAD 0.18) + + psets = {} + for key,value in obj.IfcProperties.items(): + pset, pname, ptype, pvalue = getPropertyData(key,value) + p = ifcbin.createIfcPropertySingleValue(str(pname),str(ptype),pvalue) + psets.setdefault(pset,[]).append(p) + for pname,props in psets.items(): + pset = ifcfile.createIfcPropertySet( + ifcopenshell.guid.new(), + history, + pname, + None, + props + ) + ifcfile.createIfcRelDefinesByProperties( + ifcopenshell.guid.new(), + history, + None, + None, + [product], + pset + ) + + elif obj.IfcProperties.TypeId == 'Spreadsheet::Sheet': + + # IfcProperties is a spreadsheet (deprecated) + + sheet = obj.IfcProperties + propertiesDic = {} + categories = [] + n=2 + cell = True + while cell is True: + if hasattr(sheet,'A'+str(n)): + cat = sheet.get('A'+str(n)) + key = sheet.get('B'+str(n)) + tp = sheet.get('C'+str(n)) + if hasattr(sheet,'D'+str(n)): + val = sheet.get('D'+str(n)) + else: + val = '' + if six.PY2 and isinstance(key, six.text_type): + key = key.encode("utf8") + else: + key = str(key) + if six.PY2 and isinstance(tp, six.text_type): + tp = tp.encode("utf8") + else: + tp = str(tp) + #tp = tp.encode("utf8") + if tp in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']: + val = val.encode("utf8") + elif tp == "IfcBoolean": + if val == 'True': + val = True + else: + val = False + elif tp == "IfcInteger": + val = int(val) + else: + val = float(val) + unit = None + #unit = sheet.get('E'+str(n)) + if cat in categories: + propertiesDic[cat].append({"key":key,"tp":tp,"val":val,"unit":unit}) + else: + propertiesDic[cat] = [{"key":key,"tp":tp,"val":val,"unit":unit}] + categories.append(cat) + n += 1 + else: + cell = False + for cat in propertiesDic: + props = [] + for prop in propertiesDic[cat]: + if DEBUG: + print("key",prop["key"],type(prop["key"])) + print("tp",prop["tp"],type(prop["tp"])) + print("val",prop["val"],type(prop["val"])) + if tp.lower().startswith("ifc"): + props.append(ifcbin.createIfcPropertySingleValue(prop["key"],prop["tp"],prop["val"])) + else: + print("Unable to create a property of type:",tp) + if props: + pset = ifcfile.createIfcPropertySet( + ifcopenshell.guid.new(), + history,cat, + None, + props + ) + ifcfile.createIfcRelDefinesByProperties( + ifcopenshell.guid.new(), + history, + None, + None, + [product], + pset + ) + + if hasattr(obj,"IfcData"): + + if obj.IfcData: + ifcprop = True + #if DEBUG : print(" adding ifc attributes") + props = [] + for key in obj.IfcData: + if not (key in ["attributes","IfcUID","FlagForceBrep"]): + + # (deprecated) properties in IfcData dict are stored as "key":"type(value)" + + r = obj.IfcData[key].strip(")").split("(") + if len(r) == 1: + tp = "IfcText" + val = r[0] + else: + tp = r[0] + val = "(".join(r[1:]) + val = val.strip("'") + val = val.strip('"') + #if DEBUG: print(" property ",key," : ",val.encode("utf8"), " (", str(tp), ")") + if tp in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']: + if six.PY2: + val = val.encode("utf8") + elif tp == "IfcBoolean": + if val == ".T.": + val = True + else: + val = False + elif tp == "IfcInteger": + val = int(val) + else: + val = float(val) + props.append(ifcbin.createIfcPropertySingleValue(str(key),str(tp),val)) + if props: + pset = ifcfile.createIfcPropertySet( + ifcopenshell.guid.new(), + history, + 'PropertySet', + None, + props + ) + ifcfile.createIfcRelDefinesByProperties( + ifcopenshell.guid.new(), + history, + None, + None, + [product], + pset + ) + + if not ifcprop: + #if DEBUG : print("no ifc properties to export") + pass + + # Quantities + + if hasattr(obj,"IfcData"): + quantities = [] + if ("ExportHeight" in obj.IfcData) and obj.IfcData["ExportHeight"] and hasattr(obj,"Height"): + quantities.append(ifcfile.createIfcQuantityLength('Height',None,None,obj.Height.Value/1000.0)) + if ("ExportWidth" in obj.IfcData) and obj.IfcData["ExportWidth"] and hasattr(obj,"Width"): + quantities.append(ifcfile.createIfcQuantityLength('Width',None,None,obj.Width.Value/1000.0)) + if ("ExportLength" in obj.IfcData) and obj.IfcData["ExportLength"] and hasattr(obj,"Length"): + quantities.append(ifcfile.createIfcQuantityLength('Length',None,None,obj.Length.Value/1000.0)) + if ("ExportHorizontalArea" in obj.IfcData) and obj.IfcData["ExportHorizontalArea"] and hasattr(obj,"HorizontalArea"): + quantities.append(ifcfile.createIfcQuantityArea('HorizontalArea',None,None,obj.HorizontalArea.Value/1000000.0)) + if ("ExportVerticalArea" in obj.IfcData) and obj.IfcData["ExportVerticalArea"] and hasattr(obj,"VerticalArea"): + quantities.append(ifcfile.createIfcQuantityArea('VerticalArea',None,None,obj.VerticalArea.Value/1000000.0)) + if ("ExportVolume" in obj.IfcData) and obj.IfcData["ExportVolume"] and obj.isDerivedFrom("Part::Feature"): + quantities.append(ifcfile.createIfcQuantityVolume('Volume',None,None,obj.Shape.Volume/1000000000.0)) + if quantities: + eltq = ifcfile.createIfcElementQuantity( + ifcopenshell.guid.new(), + history, + "ElementQuantities", + None, + "FreeCAD",quantities + ) + ifcfile.createIfcRelDefinesByProperties( + ifcopenshell.guid.new(), + history, + None, + None, + [product],eltq + ) + + if FULL_PARAMETRIC: + + # exporting all the object properties + + FreeCADProps = [] + FreeCADGuiProps = [] + FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCADType","IfcText",obj.TypeId)) + FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCADName","IfcText",obj.Name)) + sets = [("App",obj)] + if hasattr(obj,"Proxy"): + if obj.Proxy: + FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCADAppObject","IfcText",str(obj.Proxy.__class__))) + if FreeCAD.GuiUp: + if obj.ViewObject: + sets.append(("Gui",obj.ViewObject)) + if hasattr(obj.ViewObject,"Proxy"): + if obj.ViewObject.Proxy: + FreeCADGuiProps.append( + ifcbin.createIfcPropertySingleValue( + "FreeCADGuiObject", + "IfcText", + str(obj.ViewObject.Proxy.__class__) + ) + ) + for realm,ctx in sets: + if ctx: + for prop in ctx.PropertiesList: + if not(prop in ["IfcProperties","IfcData","Shape","Proxy","ExpressionEngine","AngularDeflection","BoundingBox"]): + try: + ptype = ctx.getTypeIdOfProperty(prop) + except AttributeError: + ptype = "Unknown" + itype = None + ivalue = None + if ptype in ["App::PropertyString","App::PropertyEnumeration"]: + itype = "IfcText" + ivalue = getattr(ctx,prop) + elif ptype == "App::PropertyInteger": + itype = "IfcInteger" + ivalue = getattr(ctx,prop) + elif ptype == "App::PropertyFloat": + itype = "IfcReal" + ivalue = float(getattr(ctx,prop)) + elif ptype == "App::PropertyBool": + itype = "IfcBoolean" + ivalue = getattr(ctx,prop) + elif ptype in ["App::PropertyVector","App::PropertyPlacement"]: + itype = "IfcText" + ivalue = str(getattr(ctx,prop)) + elif ptype in ["App::PropertyLength","App::PropertyDistance"]: + itype = "IfcReal" + ivalue = float(getattr(ctx,prop).getValueAs("m")) + elif ptype == "App::PropertyArea": + itype = "IfcReal" + ivalue = float(getattr(ctx,prop).getValueAs("m^2")) + elif ptype == "App::PropertyLink": + t = getattr(ctx,prop) + if t: + itype = "IfcText" + ivalue = "FreeCADLink_" + t.Name + else: + if DEBUG: print("Unable to encode property ",prop," of type ",ptype) + if itype: + # TODO add description + if realm == "Gui": + FreeCADGuiProps.append(ifcbin.createIfcPropertySingleValue("FreeCADGui_"+prop,itype,ivalue)) + else: + FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCAD_"+prop,itype,ivalue)) + if FreeCADProps: + pset = ifcfile.createIfcPropertySet( + ifcopenshell.guid.new(), + history,'FreeCADPropertySet', + None, + FreeCADProps + ) + ifcfile.createIfcRelDefinesByProperties( + ifcopenshell.guid.new(), + history, + None, + None, + [product], + pset + ) + if FreeCADGuiProps: + pset = ifcfile.createIfcPropertySet( + ifcopenshell.guid.new(), + history, + 'FreeCADGuiPropertySet', + None, + FreeCADGuiProps + ) + ifcfile.createIfcRelDefinesByProperties( + ifcopenshell.guid.new(), + history, + None, + None, + [product], + pset + ) + + count += 1 + + # relationships + + sites = [] + buildings = [] + floors = [] + treated = [] + defaulthost = [] + + # buildingParts can be exported as any "normal" IFC type. In that case, gather their elements first + + for bp in Draft.getObjectsOfType(objectslist,"BuildingPart"): + if bp.IfcType not in ["Site","Building","Building Storey","Space","Undefined"]: + if bp.Name in products: + subs = [] + for c in bp.Group: + if c.Name in products: + subs.append(products[c.Name]) + treated.append(c.Name) + if subs: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'Assembly', + '', + products[bp.Name], + subs + ) + + # storeys + + for floor in Draft.getObjectsOfType(objectslist,"Floor")+Draft.getObjectsOfType(objectslist,"BuildingPart"): + if (Draft.getType(floor) == "Floor") or (hasattr(floor,"IfcType") and floor.IfcType == "Building Storey"): + objs = Draft.getGroupContents(floor,walls=True,addgroups=True) + objs = Arch.pruneIncluded(objs) + children = [] + for c in objs: + if c.Name != floor.Name: # getGroupContents + addgroups will include the floor itself + if c.Name in products.keys(): + if not (c.Name in treated): + children.append(products[c.Name]) + treated.append(c.Name) + f = products[floor.Name] + if children: + ifcfile.createIfcRelContainedInSpatialStructure( + ifcopenshell.guid.new(), + history, + 'StoreyLink', + '', + children, + f + ) + floors.append(f) + defaulthost = f + + # buildings + + for building in Draft.getObjectsOfType(objectslist,"Building")+Draft.getObjectsOfType(objectslist,"BuildingPart"): + if (Draft.getType(building) == "Building") or (hasattr(building,"IfcType") and building.IfcType == "Building"): + objs = Draft.getGroupContents(building,walls=True,addgroups=True) + objs = Arch.pruneIncluded(objs) + children = [] + childfloors = [] + for c in objs: + if not (c.Name in treated): + if c.Name != building.Name: # getGroupContents + addgroups will include the building itself + if c.Name in products.keys(): + if Draft.getType(c) in ["Floor","BuildingPart"]: + childfloors.append(products[c.Name]) + treated.append(c.Name) + elif not (c.Name in treated): + children.append(products[c.Name]) + treated.append(c.Name) + b = products[building.Name] + if children: + ifcfile.createIfcRelContainedInSpatialStructure( + ifcopenshell.guid.new(), + history, + 'BuildingLink', + '', + children, + b + ) + if childfloors: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'BuildingLink', + '', + b, + childfloors + ) + buildings.append(b) + if not defaulthost and not ADD_DEFAULT_STOREY: + defaulthost = b + + # sites + + for site in Draft.getObjectsOfType(objectslist,"Site"): + objs = Draft.getGroupContents(site,walls=True,addgroups=True) + objs = Arch.pruneIncluded(objs) + children = [] + childbuildings = [] + for c in objs: + if c.Name != site.Name: # getGroupContents + addgroups will include the building itself + if c.Name in products.keys(): + if not (c.Name in treated): + if Draft.getType(c) == "Building": + childbuildings.append(products[c.Name]) + treated.append(c.Name) + sites.append(products[site.Name]) + + # add default site, building and storey as required + + if not sites: + if ADD_DEFAULT_SITE: + if DEBUG: print("No site found. Adding default site") + sites = [ifcfile.createIfcSite( + ifcopenshell.guid.new(), + history,"Default Site", + '', + None, + None, + None, + None, + "ELEMENT", + None, + None, + None, + None, + None + )] + if sites: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'ProjectLink', + '', + project,sites + ) + if not buildings: + if ADD_DEFAULT_BUILDING: + if DEBUG: print("No building found. Adding default building") + buildings = [ifcfile.createIfcBuilding( + ifcopenshell.guid.new(), + history, + "Default Building", + '', + None, + None, + None, + None, + "ELEMENT", + None, + None, + None + )] + if buildings and (not sites): + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'ProjectLink', + '', + project,buildings + ) + if floors: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'BuildingLink', + '', + buildings[0],floors + ) + if sites and buildings: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'SiteLink', + '', + sites[0], + buildings + ) + + # treat objects that are not related to any site, building or storey + + untreated = [] + for k,v in products.items(): + if not(k in treated): + if (not buildings) or (k != buildings[0].Name): + if not(Draft.getType(FreeCAD.ActiveDocument.getObject(k)) in ["Site","Building","Floor","BuildingPart"]): + untreated.append(v) + elif Draft.getType(FreeCAD.ActiveDocument.getObject(k)) == "BuildingPart": + if not(FreeCAD.ActiveDocument.getObject(k).IfcType in ["Building","Building Storey","Site","Space","Undefined"]): + untreated.append(v) + if untreated: + if not defaulthost: + if ADD_DEFAULT_STOREY: + defaulthost = ifcfile.createIfcBuildingStorey( + ifcopenshell.guid.new(), + history, + "Default Storey", + '', + None, + None, + None, + None, + "ELEMENT", + None + ) + # if ADD_DEFAULT_STOREY is on, we need a building to host it, regardless of ADD_DEFAULT_BUILDING + if not buildings: + if DEBUG: print("No building found. Adding default building") + buildings = [ifcfile.createIfcBuilding( + ifcopenshell.guid.new(), + history, + "Default Building", + '', + None, + None, + None, + None, + "ELEMENT", + None, + None, + None + )] + if sites: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'SiteLink', + '', + sites[0], + buildings + ) + else: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'ProjectLink', + '', + project,buildings + ) + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'DefaultStoreyLink', + '', + buildings[0], + [defaulthost] + ) + elif buildings: + defaulthost = buildings[0] + if defaulthost: + ifcfile.createIfcRelContainedInSpatialStructure( + ifcopenshell.guid.new(), + history, + 'UnassignedObjectsLink', + '', + untreated, + defaulthost + ) + else: + # no default host: aggregate unassigned objects directly under the IfcProject - WARNING: NON STANDARD + if DEBUG: print("WARNING - Default building generation is disabled. You are producing a non-standard file.") + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'ProjectLink', + '', + project,untreated + ) + + # materials + + materials = {} + for m in Arch.getDocumentMaterials(): + relobjs = [] + for o in m.InList: + if hasattr(o,"Material"): + if o.Material: + if o.Material.isDerivedFrom("App::MaterialObject"): + # TODO : support multimaterials too + if o.Material.Name == m.Name: + if o.Name in products: + relobjs.append(products[o.Name]) + elif o.Name in subproducts: + relobjs.append(subproducts[o.Name]) + if relobjs: + l = m.Label + if six.PY2: + l = l.encode("utf8") + mat = ifcfile.createIfcMaterial(l) + materials[m.Label] = mat + rgb = None + if hasattr(m,"Color"): + rgb = m.Color[:3] + else: + for colorslot in ["Color","DiffuseColor","ViewColor"]: + if colorslot in m.Material: + if m.Material[colorslot]: + if m.Material[colorslot][0] == "(": + rgb = tuple([float(f) for f in m.Material[colorslot].strip("()").split(",")]) + break + if rgb: + psa = ifcbin.createIfcPresentationStyleAssignment(l,rgb[0],rgb[1],rgb[2]) + isi = ifcfile.createIfcStyledItem(None,[psa],None) + isr = ifcfile.createIfcStyledRepresentation(context,"Style","Material",[isi]) + imd = ifcfile.createIfcMaterialDefinitionRepresentation(None,None,[isr],mat) + ifcfile.createIfcRelAssociatesMaterial( + ifcopenshell.guid.new(), + history, + 'MaterialLink', + '', + relobjs, + mat + ) + + # 2D objects + + annos = {} + if EXPORT_2D: + curvestyles = {} + if annotations and DEBUG: print("exporting 2D objects...") + for anno in annotations: + 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) + if anno.isDerivedFrom("Part::Feature"): + reps = [] + sh = anno.Shape.copy() + sh.scale(0.001) # 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"): + l = FreeCAD.Vector(anno.Position).multiply(0.001) + pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) + tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) + s = ";".join(anno.LabelText) + if six.PY2: + s = s.encode("utf8") + txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT") + reps = [txt] + elif Draft.getType(anno) == "DraftText": + l = FreeCAD.Vector(anno.Placement.Base).multiply(0.001) + pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) + tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) + s = ";".join(anno.Text) + if six.PY2: + s = s.encode("utf8") + txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT") + reps = [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 + if six.PY2: + l = l.encode("utf8") + ann = ifcfile.createIfcAnnotation( + ifcopenshell.guid.new(), + history,l, + '', + None, + gpl, + rep + ) + annos[anno.Name] = ann + + # groups + + sortedgroups = [] + swallowed = [] + while groups: + for g in groups.keys(): + okay = True + for c in groups[g]: + if Draft.getType(FreeCAD.ActiveDocument.getObject(c)) in ["Group","VisGroup"]: + okay = False + for s in sortedgroups: + if s[0] == c: + okay = True + if okay: + sortedgroups.append([g,groups[g]]) + for g in sortedgroups: + if g[0] in groups.keys(): + del groups[g[0]] + #print("sorted groups:",sortedgroups) + containers = {} + for g in sortedgroups: + if g[1]: + children = [] + for o in g[1]: + if o in products.keys(): + children.append(products[o]) + elif o in annos.keys(): + children.append(annos[o]) + swallowed.append(annos[o]) + if children: + name = FreeCAD.ActiveDocument.getObject(g[0]).Label + if six.PY2: + name = name.encode("utf8") + grp = ifcfile.createIfcGroup( + ifcopenshell.guid.new(), + history, + name, + '', + None + ) + products[g[0]] = grp + spatialelements[g[0]] = grp + ass = ifcfile.createIfcRelAssignsToGroup( + ifcopenshell.guid.new(), + history, + 'GroupLink', + '', + children, + None, + grp + ) + + # stack groups inside containers + + stack = {} + for g in sortedgroups: + go = FreeCAD.ActiveDocument.getObject(g[0]) + for parent in go.InList: + if hasattr(parent,"Group") and (go in parent.Group): + if (parent.Name in spatialelements) and (g[0] in spatialelements): + stack.setdefault(parent.Name,[]).append(spatialelements[g[0]]) + for k,v in stack.items(): + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'GroupStackLink', + '', + spatialelements[k], + v + ) + + # add remaining 2D objects to default host + + if annos: + remaining = [anno for anno in annos.values() if anno not in swallowed] + if remaining: + if not defaulthost: + if ADD_DEFAULT_STOREY: + defaulthost = ifcfile.createIfcBuildingStorey( + ifcopenshell.guid.new(), + history, + "Default Storey", + '', + None, + None, + None, + None, + "ELEMENT", + None + ) + # if ADD_DEFAULT_STOREY is on, we need a building to host it, regardless of ADD_DEFAULT_BUILDING + if not buildings: + buildings = [ifcfile.createIfcBuilding( + ifcopenshell.guid.new(), + history, + "Default Building", + '', + None, + None, + None, + None, + "ELEMENT", + None, + None, + None + )] + if sites: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'SiteLink', + '', + sites[0], + buildings + ) + else: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'ProjectLink', + '', + project,buildings + ) + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'DefaultStoreyLink', + '', + buildings[0], + [defaulthost] + ) + elif ADD_DEFAULT_BUILDING: + if not buildings: + defaulthost = ifcfile.createIfcBuilding( + ifcopenshell.guid.new(), + history, + "Default Building", + '', + None, + None, + None, + None, + "ELEMENT", + None, + None, + None + ) + if sites: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'SiteLink', + '', + sites[0], + [defaulthost] + ) + else: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'ProjectLink', + '', + project, + [defaulthost] + ) + if defaulthost: + ifcfile.createIfcRelContainedInSpatialStructure( + ifcopenshell.guid.new(), + history, + 'AnnotationsLink', + '', + remaining, + defaulthost + ) + else: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'ProjectLink', + '', + project, + remaining + ) + + if DEBUG: print("writing ",filename,"...") + + filename = decode(filename) + + ifcfile.write(filename) + + if STORE_UID: + # some properties might have been changed + FreeCAD.ActiveDocument.recompute() + + os.remove(templatefile) + + if DEBUG and ifcbin.compress: + f = pyopen(filename,"r") + s = len(f.read().split("\n")) + f.close() + print("Compression ratio:",int((float(ifcbin.spared)/(s+ifcbin.spared))*100),"%") + del ifcbin + + +# ************************************************************************************************ +# ********** helper for export IFC ************** + +def getPropertyData(key,value): + + # in 0.18, properties in IfcProperties dict are stored as "key":"pset;;type;;value" or "key":"type;;value" + # in 0.19, key = name;;pset, value = ptype;;value (because there can be several props with same name) + + pset = None + pname = key + if ";;" in pname: + pname = key.split(";;")[0] + pset = key.split(";;")[-1] + value = value.split(";;") + if len(value) == 3: + pset = value[0] + ptype = value[1] + pvalue = value[2] + elif len(value) == 2: + if not pset: + pset = "Default property set" + ptype = value[0] + pvalue = value[1] + else: + if DEBUG:print(" unable to export property:",pname,value) + return pset, pname, ptype, None + + #if DEBUG: print(" property ",pname," : ",pvalue.encode("utf8"), " (", str(ptype), ") in ",pset) + if ptype in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']: + if six.PY2: + pvalue = pvalue.encode("utf8") + elif ptype == "IfcBoolean": + if pvalue == ".T.": + pvalue = True + else: + pvalue = False + elif ptype == "IfcLogical": + if pvalue.upper() == "TRUE": + pvalue = True + else: + pvalue = False + elif ptype == "IfcInteger": + pvalue = int(pvalue) + else: + try: + pvalue = float(pvalue) + except: + try: + pvalue = FreeCAD.Units.Quantity(pvalue).Value + except: + if six.PY2: + pvalue = pvalue.encode("utf8") + if DEBUG:print(" warning: unable to export property as numeric value:",pname,pvalue) + + # print('pset: {}, pname: {}, ptype: {}, pvalue: {}'.format(pset, pname, ptype, pvalue)) + return pset, pname, ptype, pvalue + + +def isStandardCase(obj,ifctype): + + if ifctype.endswith("StandardCase"): + return False # type is already standard case, return False so "StandardCase" is not added twice + if hasattr(obj,"Proxy") and hasattr(obj.Proxy,"isStandardCase"): + return obj.Proxy.isStandardCase(obj) + return False + + +def getIfcTypeFromObj(obj): + + if (Draft.getType(obj) == "BuildingPart") and hasattr(obj,"IfcType") and (obj.IfcType == "Undefined"): + ifctype = "IfcBuildingStorey" # export BuildingParts as Storeys if their type wasn't explicitly set + elif hasattr(obj,"IfcType"): + ifctype = obj.IfcType.replace(" ","") + else: + ifctype = Draft.getType(obj) + + if ifctype in translationtable.keys(): + ifctype = translationtable[ifctype] + if ifctype == "VisGroup": + ifctype = "Group" + + return "Ifc" + ifctype + + +def exportIFC2X3Attributes(obj, kwargs): + + ifctype = getIfcTypeFromObj(obj) + if ifctype in ["IfcSlab", "IfcFooting"]: + kwargs.update({"PredefinedType": "NOTDEFINED"}) + elif ifctype == "IfcBuilding": + kwargs.update({"CompositionType": "ELEMENT"}) + elif ifctype == "IfcBuildingStorey": + kwargs.update({"CompositionType": "ELEMENT"}) + elif ifctype == "IfcBuildingElementProxy": + kwargs.update({"CompositionType": "ELEMENT"}) + elif ifctype == "IfcSpace": + internal = "NOTDEFINED" + if hasattr(obj,"Internal"): + if obj.Internal: + internal = "INTERNAL" + else: + internal = "EXTERNAL" + kwargs.update({ + "CompositionType": "ELEMENT", + "InteriorOrExteriorSpace": internal, + "ElevationWithFlooring": obj.Shape.BoundBox.ZMin/1000.0 + }) + elif ifctype == "IfcReinforcingBar": + kwargs.update({ + "NominalDiameter": obj.Diameter.Value, + "BarLength": obj.Length.Value + }) + elif ifctype == "IfcBuildingStorey": + kwargs.update({"Elevation": obj.Placement.Base.z/1000.0}) + return kwargs + + +def exportIfcAttributes(obj, kwargs): + + for property in obj.PropertiesList: + if obj.getGroupOfProperty(property) == "IFC Attributes" and obj.getPropertyByName(property): + value = obj.getPropertyByName(property) + if isinstance(value, FreeCAD.Units.Quantity): + value = float(value) + if property in ["ElevationWithFlooring","Elevation"]: + value = value/1000 # some properties must be changed to meters + kwargs.update({property: value}) + return kwargs + + +def buildAddress(obj,ifcfile): + + a = obj.Address or None + p = obj.PostalCode or None + t = obj.City or None + r = obj.Region or None + c = obj.Country or None + if six.PY2: + if a: + a = a.encode("utf8") + if p: + p = p.encode("utf8") + if t: + t = t.encode("utf8") + if r: + r = r.encode("utf8") + if c: + c = c.encode("utf8") + if a or p or t or r or c: + addr = ifcfile.createIfcPostalAddress("SITE",'Site Address','',None,[a],None,t,r,p,c) + else: + addr = None + return addr + + +def createCurve(ifcfile,wire): + + "creates an IfcCompositeCurve from a shape" + + segments = [] + pol = None + last = None + if wire.ShapeType == "edge": + edges = [wire] + else: + edges = Part.__sortEdges__(wire.Edges) + for e in edges: + if isinstance(e.Curve,Part.Circle): + xaxis = e.Curve.XAxis + zaxis = e.Curve.Axis + follow = True + if last: + if not DraftVecUtils.equals(last,e.Vertexes[0].Point): + follow = False + last = e.Vertexes[0].Point + prev = e.Vertexes[-1].Point + else: + last = e.Vertexes[-1].Point + prev = e.Vertexes[0].Point + else: + last = e.Vertexes[-1].Point + prev = e.Vertexes[0].Point + p1 = math.degrees(-DraftVecUtils.angle(prev.sub(e.Curve.Center),xaxis,zaxis)) + p2 = math.degrees(-DraftVecUtils.angle(last.sub(e.Curve.Center),xaxis,zaxis)) + da = DraftVecUtils.angle(e.valueAt(e.FirstParameter+0.1).sub(e.Curve.Center),prev.sub(e.Curve.Center)) + #print("curve params:",p1,",",p2,"da=",da) + if p1 < 0: + p1 = 360 + p1 + if p2 < 0: + p2 = 360 + p2 + if da > 0: + #follow = not(follow) # now we always draw segments in the correct order, so follow is always true + pass + #print(" circle from",prev,"to",last,"a1=",p1,"a2=",p2) + ovc = ifcbin.createIfcCartesianPoint(tuple(e.Curve.Center)) + zvc = ifcbin.createIfcDirection(tuple(zaxis)) + xvc = ifcbin.createIfcDirection(tuple(xaxis)) + plc = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc) + cir = ifcfile.createIfcCircle(plc,e.Curve.Radius) + curve = ifcfile.createIfcTrimmedCurve( + cir, + [ifcfile.createIfcParameterValue(p1)], + [ifcfile.createIfcParameterValue(p2)], + follow, + "PARAMETER" + ) + else: + verts = [vertex.Point for vertex in e.Vertexes] + if last: + if not DraftVecUtils.equals(last,verts[0]): + verts.reverse() + last = e.Vertexes[0].Point + else: + last = e.Vertexes[-1].Point + else: + last = e.Vertexes[-1].Point + #print(" polyline:",verts) + pts = [ifcbin.createIfcCartesianPoint(tuple(v)) for v in verts] + curve = ifcbin.createIfcPolyline(pts) + segment = ifcfile.createIfcCompositeCurveSegment("CONTINUOUS",True,curve) + segments.append(segment) + if segments: + pol = ifcfile.createIfcCompositeCurve(segments,False) + return pol + + +def getEdgesAngle(edge1, edge2): + + """ getEdgesAngle(edge1, edge2): returns a angle between two edges.""" + + vec1 = vec(edge1) + vec2 = vec(edge2) + angle = vec1.getAngle(vec2) + angle = math.degrees(angle) + return angle + + +def checkRectangle(edges): + + """ checkRectangle(edges=[]): This function checks whether the given form is a rectangle + or not. It will return True when edges form a rectangular shape or return False + when edges do not form a rectangular shape.""" + + if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("DisableIfcRectangleProfileDef",False): + return False + if len(edges) != 4: + return False + angles = [ + round(getEdgesAngle(edges[0], edges[1])), + round(getEdgesAngle(edges[0], edges[2])), + round(getEdgesAngle(edges[0], edges[3])) + ] + if angles.count(90) == 2 and (angles.count(180) == 1 or angles.count(0) == 1): + return True + return False + + +def getProfile(ifcfile,p): + + """returns an IFC profile definition from a shape""" + + import Part + import DraftGeomUtils + profile = None + if len(p.Edges) == 1: + pxvc = ifcbin.createIfcDirection((1.0,0.0)) + povc = ifcbin.createIfcCartesianPoint((0.0,0.0)) + pt = ifcbin.createIfcAxis2Placement2D(povc,pxvc) + if isinstance(p.Edges[0].Curve,Part.Circle): + # extruded circle + profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt,p.Edges[0].Curve.Radius) + elif isinstance(p.Edges[0].Curve,Part.Ellipse): + # extruded ellipse + profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt,p.Edges[0].Curve.MajorRadius,p.Edges[0].Curve.MinorRadius) + elif (checkRectangle(p.Edges)): + # arbitrarily use the first edge as the rectangle orientation + d = vec(p.Edges[0]) + d.normalize() + pxvc = ifcbin.createIfcDirection(tuple(d)) + povc = ifcbin.createIfcCartesianPoint(tuple(p.CenterOfMass)) + pt = ifcbin.createIfcAxis2Placement2D(povc,pxvc) + #semiPerimeter = p.Length/2 + #diff = math.sqrt(semiPerimeter**2 - 4*p.Area) + #b = max(abs((semiPerimeter + diff)/2),abs((semiPerimeter - diff)/2)) + #h = min(abs((semiPerimeter + diff)/2),abs((semiPerimeter - diff)/2)) + b = p.Edges[0].Length + h = p.Edges[1].Length + profile = ifcfile.createIfcRectangleProfileDef("AREA",'rectangular',pt,b,h) + elif (len(p.Faces) == 1) and (len(p.Wires) > 1): + # face with holes + f = p.Faces[0] + if DraftGeomUtils.hasCurves(f.OuterWire): + outerwire = createCurve(ifcfile,f.OuterWire) + else: + w = Part.Wire(Part.__sortEdges__(f.OuterWire.Edges)) + pts = [ifcbin.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] + outerwire = ifcbin.createIfcPolyline(pts) + innerwires = [] + for w in f.Wires: + if w.hashCode() != f.OuterWire.hashCode(): + if DraftGeomUtils.hasCurves(w): + innerwires.append(createCurve(ifcfile,w)) + else: + w = Part.Wire(Part.__sortEdges__(w.Edges)) + pts = [ifcbin.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] + innerwires.append(ifcbin.createIfcPolyline(pts)) + profile = ifcfile.createIfcArbitraryProfileDefWithVoids("AREA",None,outerwire,innerwires) + else: + if DraftGeomUtils.hasCurves(p): + # extruded composite curve + pol = createCurve(ifcfile,p) + else: + # extruded polyline + w = Part.Wire(Part.__sortEdges__(p.Wires[0].Edges)) + pts = [ifcbin.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] + pol = ifcbin.createIfcPolyline(pts) + profile = ifcfile.createIfcArbitraryClosedProfileDef("AREA",None,pol) + return profile + + +def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None): + + """returns an IfcShapeRepresentation object or None""" + + import Part + import DraftGeomUtils + import DraftVecUtils + shapes = [] + placement = None + productdef = None + shapetype = "no shape" + tostore = False + subplacement = None + + # check for clones + + if (not subtraction) and (not forcebrep): + 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() + 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(0.001))) + transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3) + mapitem = ifcfile.createIfcMappedItem(repmap,transf) + shapes = [mapitem] + solidType = "MappedRepresentation" + shapetype = "clone" + else: + # base shape not yet created + tostore = k + + # unhandled case: object is duplicated because of Axis + 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): + profile = None + ev = FreeCAD.Vector() + if hasattr(obj,"Proxy"): + if hasattr(obj.Proxy,"getRebarData"): + # export rebars as IfcSweptDiskSolid + rdata = obj.Proxy.getRebarData(obj) + if rdata: + # convert to meters + r = rdata[1] * 0.001 + for w in rdata[0]: + w.scale(0.001) + cur = createCurve(ifcfile,w) + shape = ifcfile.createIfcSweptDiskSolid(cur,r) + shapes.append(shape) + solidType = "SweptSolid" + shapetype = "extrusion" + if (not shapes) and hasattr(obj.Proxy,"getExtrusionData"): + extdata = obj.Proxy.getExtrusionData(obj) + if extdata: + #print(extdata) + # convert to meters + p = extdata[0] + if not isinstance(p,list): + p = [p] + ev = extdata[1] + if not isinstance(ev,list): + ev = [ev] + pl = extdata[2] + if not isinstance(pl,list): + pl = [pl] + for i in range(len(p)): + pi = p[i] + pi.scale(0.001) + if i < len(ev): + evi = FreeCAD.Vector(ev[i]) + else: + evi = FreeCAD.Vector(ev[-1]) + evi.multiply(0.001) + if i < len(pl): + pli = pl[i].copy() + else: + pli = pl[-1].copy() + pli.Base = pli.Base.multiply(0.001) + pstr = str([v.Point for v in p[i].Vertexes]) + if pstr in profiledefs: + profile = profiledefs[pstr] + shapetype = "reusing profile" + else: + profile = getProfile(ifcfile,pi) + if profile: + profiledefs[pstr] = profile + if profile and not(DraftVecUtils.isNull(evi)): + #ev = pl.Rotation.inverted().multVec(evi) + #print("evi:",evi) + if not tostore: + # add the object placement to the profile placement. Otherwise it'll be done later at map insert + pl2 = obj.getGlobalPlacement() + pl2.Base = pl2.Base.multiply(0.001) + pli = pl2.multiply(pli) + xvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(1,0,0)))) + zvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(0,0,1)))) + ovc = ifcbin.createIfcCartesianPoint(tuple(pli.Base)) + lpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc) + edir = ifcbin.createIfcDirection(tuple(FreeCAD.Vector(evi).normalize())) + shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,evi.Length) + shapes.append(shape) + solidType = "SweptSolid" + shapetype = "extrusion" + + if not shapes: + + # check if we keep a null shape (additions-only object) + + if (hasattr(obj,"Base") and hasattr(obj,"Width") and hasattr(obj,"Height")) \ + and (not obj.Base) \ + and obj.Additions \ + and (not obj.Width.Value) \ + and (not obj.Height.Value): + shapes = None + + else: + + # brep representation + + fcshape = None + solidType = "Brep" + if subtraction: + if hasattr(obj,"Proxy"): + if hasattr(obj.Proxy,"getSubVolume"): + fcshape = obj.Proxy.getSubVolume(obj) + if not fcshape: + if obj.isDerivedFrom("Part::Feature"): + #if hasattr(obj,"Base") and hasattr(obj,"Additions")and hasattr(obj,"Subtractions"): + if False: # above is buggy. No way to duplicate shapes that way? + if obj.Base and (not obj.Additions) and not(obj.Subtractions): + if obj.Base.isDerivedFrom("Part::Feature"): + if obj.Base.Shape: + if obj.Base.Shape.Solids: + fcshape = obj.Base.Shape + subplacement = FreeCAD.Placement(obj.Placement) + if not fcshape: + if obj.Shape: + if not obj.Shape.isNull(): + fcshape = obj.Shape.copy() + fcshape.Placement = obj.getGlobalPlacement() + if fcshape: + shapedef = str([v.Point for v in fcshape.Vertexes]) + if shapedef in shapedefs: + shapes = shapedefs[shapedef] + shapetype = "reusing brep" + else: + + # new ifcopenshell serializer + + from ifcopenshell import geom + serialized = False + if hasattr(geom,"serialise") and obj.isDerivedFrom("Part::Feature") and SERIALIZE: + if obj.Shape.Faces: + sh = obj.Shape.copy() + sh.Placement = obj.getGlobalPlacement() + sh.scale(0.001) # to meters + p = geom.serialise(sh.exportBrepToString()) + if p: + productdef = ifcfile.add(p) + for rep in productdef.Representations: + rep.ContextOfItems = context + 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) + shapetype = "advancedbrep" + shapes = None + serialized = True + + if not serialized: + + # old method + + solids = [] + + # if this is a clone, place back the shape in null position + if tostore: + fcshape.Placement = FreeCAD.Placement() + + if fcshape.Solids: + dataset = fcshape.Solids + else: + dataset = fcshape.Shells + #if DEBUG: print("Warning! object contains no solids") + + for fcsolid in dataset: + fcsolid.scale(0.001) # to meters + faces = [] + curves = False + shapetype = "brep" + for fcface in fcsolid.Faces: + for e in fcface.Edges: + if DraftGeomUtils.geomType(e) != "Line": + from FreeCAD import Base + try: + if e.curvatureAt(e.FirstParameter+(e.LastParameter-e.FirstParameter)/2) > 0.0001: + curves = True + break + except Part.OCCError: + pass + except Base.FreeCADError: + pass + if curves: + joinfacets = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcJoinCoplanarFacets",False) + usedae = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcUseDaeOptions",False) + if joinfacets: + result = Arch.removeCurves(fcsolid,dae=usedae) + if result: + fcsolid = result + else: + # fall back to standard triangulation + joinfacets = False + if not joinfacets: + shapetype = "triangulated" + if usedae: + import importDAE + tris = importDAE.triangulate(fcsolid) + else: + tris = fcsolid.tessellate(tessellation) + for tri in tris[1]: + pts = [ifcbin.createIfcCartesianPoint(tuple(tris[0][i])) for i in tri] + loop = ifcbin.createIfcPolyLoop(pts) + bound = ifcfile.createIfcFaceOuterBound(loop,True) + face = ifcfile.createIfcFace([bound]) + faces.append(face) + fcsolid = Part.Shape() # empty shape so below code is not executed + + for fcface in fcsolid.Faces: + loops = [] + verts = [v.Point for v in fcface.OuterWire.OrderedVertexes] + c = fcface.CenterOfMass + v1 = verts[0].sub(c) + v2 = verts[1].sub(c) + try: + n = fcface.normalAt(0,0) + except Part.OCCError: + continue # this is a very wrong face, it probably shouldn't be here... + if DraftVecUtils.angle(v2,v1,n) >= 0: + verts.reverse() # inverting verts order if the direction is couterclockwise + pts = [ifcbin.createIfcCartesianPoint(tuple(v)) for v in verts] + loop = ifcbin.createIfcPolyLoop(pts) + bound = ifcfile.createIfcFaceOuterBound(loop,True) + loops.append(bound) + for wire in fcface.Wires: + if wire.hashCode() != fcface.OuterWire.hashCode(): + verts = [v.Point for v in wire.OrderedVertexes] + if len(verts) > 1: + v1 = verts[0].sub(c) + v2 = verts[1].sub(c) + if DraftVecUtils.angle(v2,v1,DraftVecUtils.neg(n)) >= 0: + verts.reverse() + pts = [ifcbin.createIfcCartesianPoint(tuple(v)) for v in verts] + loop = ifcbin.createIfcPolyLoop(pts) + bound = ifcfile.createIfcFaceBound(loop,True) + loops.append(bound) + else: + print("Warning: wire with one/no vertex in ", obj.Label) + face = ifcfile.createIfcFace(loops) + faces.append(face) + + if faces: + shell = ifcfile.createIfcClosedShell(faces) + shape = ifcfile.createIfcFacetedBrep(shell) + shapes.append(shape) + + shapedefs[shapedef] = shapes + + if shapes: + + colorshapes = shapes # to keep track of individual shapes for coloring below + if tostore: + subrep = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes) + 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) + repmap = ifcfile.createIfcRepresentationMap(gpl,subrep) + pla = obj.getGlobalPlacement() + axis1 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0)))) + axis2 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0)))) + origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(0.001))) + axis3 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1)))) + transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3) + mapitem = ifcfile.createIfcMappedItem(repmap,transf) + shapes = [mapitem] + sharedobjects[tostore] = repmap + solidType = "MappedRepresentation" + + # set surface style + + shapecolor = None + diffusecolor = None + transparency = 0.0 + if colors: + # color dict is given + if obj.Name in colors: + color = colors[obj.Name] + shapecolor = color + if isinstance(color[0],tuple): + # this is a diffusecolor. For now, use the first color - #TODO: Support per-face colors + diffusecolor = color + shapecolor = color[0] + elif FreeCAD.GuiUp and (not subtraction) and hasattr(obj.ViewObject,"ShapeColor"): + # every object gets a surface style. If the obj has a material, the surfstyle + # is named after it. Revit will treat surfacestyles as materials (and discard + # actual ifcmaterial) + shapecolor = obj.ViewObject.ShapeColor[:3] + transparency = obj.ViewObject.Transparency/100.0 + if hasattr(obj.ViewObject,"DiffuseColor"): + diffusecolor = obj.ViewObject.DiffuseColor + if shapecolor and (shapetype != "clone"): # cloned objects are already colored + key = None + rgbt = [shapecolor+(transparency,) for shape in shapes] + if diffusecolor \ + and (len(diffusecolor) == len(obj.Shape.Faces)) \ + and (len(obj.Shape.Solids) == len(colorshapes)): + i = 0 + rgbt = [] + for sol in obj.Shape.Solids: + rgbt.append(diffusecolor[i]) + i += len(sol.Faces) + for i,shape in enumerate(colorshapes): + key = rgbt[i] + #if hasattr(obj,"Material"): + # if obj.Material: + # key = obj.Material.Name #TODO handle multimaterials + if key in surfstyles: + psa = surfstyles[key] + else: + m = None + if hasattr(obj,"Material"): + if obj.Material: + m = obj.Material.Label + if six.PY2: + m = m.encode("utf8") + psa = ifcbin.createIfcPresentationStyleAssignment(m,rgbt[i][0],rgbt[i][1],rgbt[i][2],rgbt[i][3]) + surfstyles[key] = psa + isi = ifcfile.createIfcStyledItem(shape,[psa],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) + representation = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes) + productdef = ifcfile.createIfcProductDefinitionShape(None,None,[representation]) + + return productdef,placement,shapetype diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index 674851482e..c51a7c8520 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -24,14 +24,11 @@ from __future__ import print_function __title__ = "FreeCAD IFC importer - Enhanced ifcopenshell-only version" -__author__ = "Yorik van Havre","Jonathan Wiedemann" +__author__ = "Yorik van Havre","Jonathan Wiedemann","Bernd Hahnebach" __url__ = "http://www.freecadweb.org" import six import os -import time -import tempfile -import uuid import math import sys @@ -41,13 +38,12 @@ import Draft import Arch import DraftVecUtils import ArchIFCSchema -from DraftGeomUtils import vec ## @package importIFC # \ingroup ARCH -# \brief IFC file format importer and exporter +# \brief IFC file format importer # -# This module provides tools to import and export IFC files. +# This module provides tools to import IFC files. DEBUG = False # Set to True to see debug messages. Otherwise, totally silent ZOOMOUT = True # Set to False to not zoom extents after import @@ -140,52 +136,6 @@ structuralifcobjects = ( "IfcStructuralPlanarAction" ) -# specific FreeCAD <-> IFC slang translations -translationtable = { - "Foundation":"Footing", - "Floor":"BuildingStorey", - "Rebar":"ReinforcingBar", - "HydroEquipment":"SanitaryTerminal", - "ElectricEquipment":"ElectricAppliance", - "Furniture":"FurnishingElement", - "Stair Flight":"StairFlight", - "Curtain Wall":"CurtainWall", - "Pipe Segment":"PipeSegment", - "Pipe Fitting":"PipeFitting" -} - -# the base IFC template for export -ifctemplate = """ISO-10303-21; -HEADER; -FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); -FILE_NAME('$filename','$timestamp',('$owner','$email'),('$company'),'IfcOpenShell','IfcOpenShell',''); -FILE_SCHEMA(('$ifcschema')); -ENDSEC; -DATA; -#1=IFCPERSON($,$,'$owner',$,$,$,$,$); -#2=IFCORGANIZATION($,'$company',$,$,$); -#3=IFCPERSONANDORGANIZATION(#1,#2,$); -#4=IFCAPPLICATION(#2,'$version','FreeCAD','118df2cf_ed21_438e_a41'); -#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,$now); -#6=IFCDIRECTION((1.,0.,0.)); -#7=IFCDIRECTION((0.,0.,1.)); -#8=IFCCARTESIANPOINT((0.,0.,0.)); -#9=IFCAXIS2PLACEMENT3D(#8,#7,#6); -#10=IFCDIRECTION((0.,1.,0.)); -#11=IFCGEOMETRICREPRESENTATIONCONTEXT('Plan','Model',3,1.E-05,#9,#10); -#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); -#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); -#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); -#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); -#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); -#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); -#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); -#19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); -#20=IFCPROJECT('$projectid',#5,'$project',$,$,$,$,(#11),#19); -ENDSEC; -END-ISO-10303-21; -""" - # ************************************************************************************************ # ********** some helper, used in import and export @@ -1539,1382 +1489,6 @@ class recycler: # ************************************************************************************************ -# ********** export IFC **************** - -def export(exportList,filename,colors=None): - - """export(exportList,filename,colors=None) -- exports FreeCAD contents to an IFC file. - colors is an optional dictionary of objName:shapeColorTuple or objName:diffuseColorList elements - to be used in non-GUI mode if you want to be able to export colors.""" - - getPreferences() - - try: - global ifcopenshell - import ifcopenshell - except: - FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n") - return - - version = FreeCAD.Version() - owner = FreeCAD.ActiveDocument.CreatedBy - email = '' - if ("@" in owner) and ("<" in owner): - s = owner.split("<") - owner = s[0].strip() - 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 - schema = ["IFC4", "IFC2X3"][FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)] - else: - schema = "IFC2X3" - if DEBUG: print("Exporting an",schema,"file...") - template = template.replace("$ifcschema",schema) - template = template.replace("$owner",owner) - template = template.replace("$company",FreeCAD.ActiveDocument.Company) - template = template.replace("$email",email) - template = template.replace("$now",str(int(time.time()))) - template = template.replace("$projectid",FreeCAD.ActiveDocument.Uid[:22].replace("-","_")) - template = template.replace("$project",FreeCAD.ActiveDocument.Name) - template = template.replace("$filename",os.path.basename(filename)) - template = template.replace("$timestamp",str(time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()))) - if hasattr(ifcopenshell,"version"): - template = template.replace("IfcOpenShell","IfcOpenShell "+ifcopenshell.version) - templatefilehandle,templatefile = tempfile.mkstemp(suffix=".ifc") - of = pyopen(templatefile,"w") - if six.PY2: - template = template.encode("utf8") - of.write(template) - of.close() - os.close(templatefilehandle) - global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs - ifcfile = ifcopenshell.open(templatefile) - history = ifcfile.by_type("IfcOwnerHistory")[0] - context = ifcfile.by_type("IfcGeometricRepresentationContext")[0] - project = ifcfile.by_type("IfcProject")[0] - objectslist = Draft.getGroupContents(exportList,walls=True,addgroups=True) - if(Draft.getObjectsOfType(objectslist, "Site")): # we assume one site and one representation context only - trueNorthX = math.tan(-Draft.getObjectsOfType(objectslist, "Site")[0].Declination.getValueAs(FreeCAD.Units.Radian)) - context.TrueNorth.DirectionRatios = (trueNorthX, 1., 1.) - annotations = [] - for obj in objectslist: - if obj.isDerivedFrom("Part::Part2DObject"): - annotations.append(obj) - elif obj.isDerivedFrom("App::Annotation") or (Draft.getType(obj) == "DraftText"): - annotations.append(obj) - elif obj.isDerivedFrom("Part::Feature"): - 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 FULL_PARAMETRIC: - objectslist = Arch.getAllChildren(objectslist) - products = {} # { Name: IfcEntity, ... } - subproducts = {} # { Name: IfcEntity, ... } for storing additions/subtractions and other types of subcomponents of a product - surfstyles = {} # { (r,g,b): IfcEntity, ... } - clones = {} # { Basename:[Clonename1,Clonename2,...] } - sharedobjects = {} # { BaseName: IfcRepresentationMap } - count = 1 - groups = {} # { Host: [Child,Child,...] } - profiledefs = {} # { ProfileDefString:profiledef,...} - shapedefs = {} # { ShapeDefString:[shapes],... } - spatialelements = {} # {Name:IfcEntity, ... } - - # reusable entity system - - global ifcbin - ifcbin = recycler(ifcfile) - - # build clones table - - if CREATE_CLONES: - for o in objectslist: - b = Draft.getCloneBase(o,strict=True) - if b: - clones.setdefault(b.Name,[]).append(o.Name) - - #print("clones table: ",clones) - #print(objectslist) - - # testing if more than one site selected (forbidden in IFC) - - if len(Draft.getObjectsOfType(objectslist,"Site")) > 1: - FreeCAD.Console.PrintError("More than one site is selected, which is forbidden by IFC standards. Please export only one site by IFC file.\n") - return - - # products - - for obj in objectslist: - - # 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 STORE_UID and hasattr(obj,"IfcData"): - d = obj.IfcData - d["IfcUID"] = uid - obj.IfcData = d - - ifctype = getIfcTypeFromObj(obj) - - if ifctype == "IfcGroup": - groups[obj.Name] = [o.Name for o in obj.Group] - continue - - # export grids - - if ifctype in ["IfcAxis","IfcAxisSystem","IfcGrid"]: - ifcaxes = [] - ifcpols = [] - if ifctype == "IfcAxis": - # make sure this axis is not included in something else already - standalone = True - for p in obj.InList: - if hasattr(p,"Axes") and (obj in p.Axes): - if p in objectslist: - axgroups = [] - standalone = False - break - if standalone: - axgroups = [obj.Proxy.getAxisData(obj)] - else: - axgroups = obj.Proxy.getAxisData(obj) - if not axgroups: - continue - ifctype = "IfcGrid" - for axg in axgroups: - ifcaxg = [] - for ax in axg: - p1 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[0]).multiply(0.001))) - p2 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[1]).multiply(0.001))) - pol = ifcbin.createIfcPolyline([p1,p2]) - ifcpols.append(pol) - axis = ifcfile.createIfcGridAxis(ax[2],pol,True) - ifcaxg.append(axis) - if len(ifcaxes) < 3: - ifcaxes.append(ifcaxg) - else: - ifcaxes[2] = ifcaxes[2]+ifcaxg # IfcGrid can have max 3 axes systems - u = None - v = None - w = None - if ifcaxes: - u = ifcaxes[0] - if len(ifcaxes) > 1: - v = ifcaxes[1] - if len(ifcaxes) > 2: - w = ifcaxes[2] - if u and v: - if DEBUG: print(str(count).ljust(3)," : ", ifctype, " (",str(len(ifcpols)),"axes ) : ",name) - 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) - plac = ifcbin.createIfcLocalPlacement(gpl) - cset = ifcfile.createIfcGeometricCurveSet(ifcpols) - #subc = ifcfile.createIfcGeometricRepresentationSubContext('FootPrint','Model',context,None,"MODEL_VIEW",None,None,None,None,None) - srep = ifcfile.createIfcShapeRepresentation(context,'FootPrint',"GeometricCurveSet",ifcpols) - pdef = ifcfile.createIfcProductDefinitionShape(None,None,[srep]) - grid = ifcfile.createIfcGrid(uid,history,name,description,None,plac,pdef,u,v,w) - products[obj.Name] = grid - count += 1 - continue - - 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 FORCE_BREP),colors=colors) - if getstd: - if isStandardCase(obj,ifctype): - ifctype += "StandardCase" - - if 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/1000.0, - "SiteAddress":buildAddress(obj,ifcfile), - "CompositionType": "ELEMENT" - }) - if schema == "IFC2X3": - kwargs = exportIFC2X3Attributes(obj, kwargs) - else: - kwargs = exportIfcAttributes(obj, kwargs) - - # creating the product - - #print(obj.Label," : ",ifctype," : ",kwargs) - product = getattr(ifcfile,"create"+ifctype)(**kwargs) - products[obj.Name] = product - if ifctype in ["IfcBuilding","IfcBuildingStorey","IfcSite","IfcSpace"]: - spatialelements[obj.Name] = product - - # additions - - if hasattr(obj,"Additions") and (shapetype in ["extrusion","no shape"]): - for o in obj.Additions: - r2,p2,c2 = getRepresentation(ifcfile,context,o,colors=colors) - if DEBUG: print(" adding ",c2," : ",o.Label) - l = o.Label - if six.PY2: - l = l.encode("utf8") - prod2 = ifcfile.createIfcBuildingElementProxy( - ifcopenshell.guid.new(), - history, - l, - None, - None, - p2, - r2, - None, - "ELEMENT" - ) - subproducts[o.Name] = prod2 - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'Addition', - '', - product, - [prod2] - ) - - # subtractions - - guests = [] - for o in obj.InList: - if hasattr(o,"Hosts"): - for co in o.Hosts: - if co == obj: - if o not in guests: - guests.append(o) - if hasattr(obj,"Subtractions") and (shapetype in ["extrusion","no shape"]): - for o in obj.Subtractions + guests: - r2,p2,c2 = getRepresentation(ifcfile,context,o,subtraction=True,colors=colors) - if DEBUG: print(" subtracting ",c2," : ",o.Label) - l = o.Label - if six.PY2: - l = l.encode("utf8") - prod2 = ifcfile.createIfcOpeningElement( - ifcopenshell.guid.new(), - history, - l, - None, - None, - p2, - r2, - None - ) - subproducts[o.Name] = prod2 - ifcfile.createIfcRelVoidsElement( - ifcopenshell.guid.new(), - history, - 'Subtraction', - '', - product, - prod2 - ) - - # properties - - ifcprop = False - if hasattr(obj,"IfcProperties"): - - if obj.IfcProperties: - - ifcprop = True - - if isinstance(obj.IfcProperties,dict): - - # IfcProperties is a dictionary (FreeCAD 0.18) - - psets = {} - for key,value in obj.IfcProperties.items(): - pset, pname, ptype, pvalue = getPropertyData(key,value) - p = ifcbin.createIfcPropertySingleValue(str(pname),str(ptype),pvalue) - psets.setdefault(pset,[]).append(p) - for pname,props in psets.items(): - pset = ifcfile.createIfcPropertySet( - ifcopenshell.guid.new(), - history, - pname, - None, - props - ) - ifcfile.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), - history, - None, - None, - [product], - pset - ) - - elif obj.IfcProperties.TypeId == 'Spreadsheet::Sheet': - - # IfcProperties is a spreadsheet (deprecated) - - sheet = obj.IfcProperties - propertiesDic = {} - categories = [] - n=2 - cell = True - while cell is True: - if hasattr(sheet,'A'+str(n)): - cat = sheet.get('A'+str(n)) - key = sheet.get('B'+str(n)) - tp = sheet.get('C'+str(n)) - if hasattr(sheet,'D'+str(n)): - val = sheet.get('D'+str(n)) - else: - val = '' - if six.PY2 and isinstance(key, six.text_type): - key = key.encode("utf8") - else: - key = str(key) - if six.PY2 and isinstance(tp, six.text_type): - tp = tp.encode("utf8") - else: - tp = str(tp) - #tp = tp.encode("utf8") - if tp in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']: - val = val.encode("utf8") - elif tp == "IfcBoolean": - if val == 'True': - val = True - else: - val = False - elif tp == "IfcInteger": - val = int(val) - else: - val = float(val) - unit = None - #unit = sheet.get('E'+str(n)) - if cat in categories: - propertiesDic[cat].append({"key":key,"tp":tp,"val":val,"unit":unit}) - else: - propertiesDic[cat] = [{"key":key,"tp":tp,"val":val,"unit":unit}] - categories.append(cat) - n += 1 - else: - cell = False - for cat in propertiesDic: - props = [] - for prop in propertiesDic[cat]: - if DEBUG: - print("key",prop["key"],type(prop["key"])) - print("tp",prop["tp"],type(prop["tp"])) - print("val",prop["val"],type(prop["val"])) - if tp.lower().startswith("ifc"): - props.append(ifcbin.createIfcPropertySingleValue(prop["key"],prop["tp"],prop["val"])) - else: - print("Unable to create a property of type:",tp) - if props: - pset = ifcfile.createIfcPropertySet( - ifcopenshell.guid.new(), - history,cat, - None, - props - ) - ifcfile.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), - history, - None, - None, - [product], - pset - ) - - if hasattr(obj,"IfcData"): - - if obj.IfcData: - ifcprop = True - #if DEBUG : print(" adding ifc attributes") - props = [] - for key in obj.IfcData: - if not (key in ["attributes","IfcUID","FlagForceBrep"]): - - # (deprecated) properties in IfcData dict are stored as "key":"type(value)" - - r = obj.IfcData[key].strip(")").split("(") - if len(r) == 1: - tp = "IfcText" - val = r[0] - else: - tp = r[0] - val = "(".join(r[1:]) - val = val.strip("'") - val = val.strip('"') - #if DEBUG: print(" property ",key," : ",val.encode("utf8"), " (", str(tp), ")") - if tp in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']: - if six.PY2: - val = val.encode("utf8") - elif tp == "IfcBoolean": - if val == ".T.": - val = True - else: - val = False - elif tp == "IfcInteger": - val = int(val) - else: - val = float(val) - props.append(ifcbin.createIfcPropertySingleValue(str(key),str(tp),val)) - if props: - pset = ifcfile.createIfcPropertySet( - ifcopenshell.guid.new(), - history, - 'PropertySet', - None, - props - ) - ifcfile.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), - history, - None, - None, - [product], - pset - ) - - if not ifcprop: - #if DEBUG : print("no ifc properties to export") - pass - - # Quantities - - if hasattr(obj,"IfcData"): - quantities = [] - if ("ExportHeight" in obj.IfcData) and obj.IfcData["ExportHeight"] and hasattr(obj,"Height"): - quantities.append(ifcfile.createIfcQuantityLength('Height',None,None,obj.Height.Value/1000.0)) - if ("ExportWidth" in obj.IfcData) and obj.IfcData["ExportWidth"] and hasattr(obj,"Width"): - quantities.append(ifcfile.createIfcQuantityLength('Width',None,None,obj.Width.Value/1000.0)) - if ("ExportLength" in obj.IfcData) and obj.IfcData["ExportLength"] and hasattr(obj,"Length"): - quantities.append(ifcfile.createIfcQuantityLength('Length',None,None,obj.Length.Value/1000.0)) - if ("ExportHorizontalArea" in obj.IfcData) and obj.IfcData["ExportHorizontalArea"] and hasattr(obj,"HorizontalArea"): - quantities.append(ifcfile.createIfcQuantityArea('HorizontalArea',None,None,obj.HorizontalArea.Value/1000000.0)) - if ("ExportVerticalArea" in obj.IfcData) and obj.IfcData["ExportVerticalArea"] and hasattr(obj,"VerticalArea"): - quantities.append(ifcfile.createIfcQuantityArea('VerticalArea',None,None,obj.VerticalArea.Value/1000000.0)) - if ("ExportVolume" in obj.IfcData) and obj.IfcData["ExportVolume"] and obj.isDerivedFrom("Part::Feature"): - quantities.append(ifcfile.createIfcQuantityVolume('Volume',None,None,obj.Shape.Volume/1000000000.0)) - if quantities: - eltq = ifcfile.createIfcElementQuantity( - ifcopenshell.guid.new(), - history, - "ElementQuantities", - None, - "FreeCAD",quantities - ) - ifcfile.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), - history, - None, - None, - [product],eltq - ) - - if FULL_PARAMETRIC: - - # exporting all the object properties - - FreeCADProps = [] - FreeCADGuiProps = [] - FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCADType","IfcText",obj.TypeId)) - FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCADName","IfcText",obj.Name)) - sets = [("App",obj)] - if hasattr(obj,"Proxy"): - if obj.Proxy: - FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCADAppObject","IfcText",str(obj.Proxy.__class__))) - if FreeCAD.GuiUp: - if obj.ViewObject: - sets.append(("Gui",obj.ViewObject)) - if hasattr(obj.ViewObject,"Proxy"): - if obj.ViewObject.Proxy: - FreeCADGuiProps.append( - ifcbin.createIfcPropertySingleValue( - "FreeCADGuiObject", - "IfcText", - str(obj.ViewObject.Proxy.__class__) - ) - ) - for realm,ctx in sets: - if ctx: - for prop in ctx.PropertiesList: - if not(prop in ["IfcProperties","IfcData","Shape","Proxy","ExpressionEngine","AngularDeflection","BoundingBox"]): - try: - ptype = ctx.getTypeIdOfProperty(prop) - except AttributeError: - ptype = "Unknown" - itype = None - ivalue = None - if ptype in ["App::PropertyString","App::PropertyEnumeration"]: - itype = "IfcText" - ivalue = getattr(ctx,prop) - elif ptype == "App::PropertyInteger": - itype = "IfcInteger" - ivalue = getattr(ctx,prop) - elif ptype == "App::PropertyFloat": - itype = "IfcReal" - ivalue = float(getattr(ctx,prop)) - elif ptype == "App::PropertyBool": - itype = "IfcBoolean" - ivalue = getattr(ctx,prop) - elif ptype in ["App::PropertyVector","App::PropertyPlacement"]: - itype = "IfcText" - ivalue = str(getattr(ctx,prop)) - elif ptype in ["App::PropertyLength","App::PropertyDistance"]: - itype = "IfcReal" - ivalue = float(getattr(ctx,prop).getValueAs("m")) - elif ptype == "App::PropertyArea": - itype = "IfcReal" - ivalue = float(getattr(ctx,prop).getValueAs("m^2")) - elif ptype == "App::PropertyLink": - t = getattr(ctx,prop) - if t: - itype = "IfcText" - ivalue = "FreeCADLink_" + t.Name - else: - if DEBUG: print("Unable to encode property ",prop," of type ",ptype) - if itype: - # TODO add description - if realm == "Gui": - FreeCADGuiProps.append(ifcbin.createIfcPropertySingleValue("FreeCADGui_"+prop,itype,ivalue)) - else: - FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCAD_"+prop,itype,ivalue)) - if FreeCADProps: - pset = ifcfile.createIfcPropertySet( - ifcopenshell.guid.new(), - history,'FreeCADPropertySet', - None, - FreeCADProps - ) - ifcfile.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), - history, - None, - None, - [product], - pset - ) - if FreeCADGuiProps: - pset = ifcfile.createIfcPropertySet( - ifcopenshell.guid.new(), - history, - 'FreeCADGuiPropertySet', - None, - FreeCADGuiProps - ) - ifcfile.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), - history, - None, - None, - [product], - pset - ) - - count += 1 - - # relationships - - sites = [] - buildings = [] - floors = [] - treated = [] - defaulthost = [] - - # buildingParts can be exported as any "normal" IFC type. In that case, gather their elements first - - for bp in Draft.getObjectsOfType(objectslist,"BuildingPart"): - if bp.IfcType not in ["Site","Building","Building Storey","Space","Undefined"]: - if bp.Name in products: - subs = [] - for c in bp.Group: - if c.Name in products: - subs.append(products[c.Name]) - treated.append(c.Name) - if subs: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'Assembly', - '', - products[bp.Name], - subs - ) - - # storeys - - for floor in Draft.getObjectsOfType(objectslist,"Floor")+Draft.getObjectsOfType(objectslist,"BuildingPart"): - if (Draft.getType(floor) == "Floor") or (hasattr(floor,"IfcType") and floor.IfcType == "Building Storey"): - objs = Draft.getGroupContents(floor,walls=True,addgroups=True) - objs = Arch.pruneIncluded(objs) - children = [] - for c in objs: - if c.Name != floor.Name: # getGroupContents + addgroups will include the floor itself - if c.Name in products.keys(): - if not (c.Name in treated): - children.append(products[c.Name]) - treated.append(c.Name) - f = products[floor.Name] - if children: - ifcfile.createIfcRelContainedInSpatialStructure( - ifcopenshell.guid.new(), - history, - 'StoreyLink', - '', - children, - f - ) - floors.append(f) - defaulthost = f - - # buildings - - for building in Draft.getObjectsOfType(objectslist,"Building")+Draft.getObjectsOfType(objectslist,"BuildingPart"): - if (Draft.getType(building) == "Building") or (hasattr(building,"IfcType") and building.IfcType == "Building"): - objs = Draft.getGroupContents(building,walls=True,addgroups=True) - objs = Arch.pruneIncluded(objs) - children = [] - childfloors = [] - for c in objs: - if not (c.Name in treated): - if c.Name != building.Name: # getGroupContents + addgroups will include the building itself - if c.Name in products.keys(): - if Draft.getType(c) in ["Floor","BuildingPart"]: - childfloors.append(products[c.Name]) - treated.append(c.Name) - elif not (c.Name in treated): - children.append(products[c.Name]) - treated.append(c.Name) - b = products[building.Name] - if children: - ifcfile.createIfcRelContainedInSpatialStructure( - ifcopenshell.guid.new(), - history, - 'BuildingLink', - '', - children, - b - ) - if childfloors: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'BuildingLink', - '', - b, - childfloors - ) - buildings.append(b) - if not defaulthost and not ADD_DEFAULT_STOREY: - defaulthost = b - - # sites - - for site in Draft.getObjectsOfType(objectslist,"Site"): - objs = Draft.getGroupContents(site,walls=True,addgroups=True) - objs = Arch.pruneIncluded(objs) - children = [] - childbuildings = [] - for c in objs: - if c.Name != site.Name: # getGroupContents + addgroups will include the building itself - if c.Name in products.keys(): - if not (c.Name in treated): - if Draft.getType(c) == "Building": - childbuildings.append(products[c.Name]) - treated.append(c.Name) - sites.append(products[site.Name]) - - # add default site, building and storey as required - - if not sites: - if ADD_DEFAULT_SITE: - if DEBUG: print("No site found. Adding default site") - sites = [ifcfile.createIfcSite( - ifcopenshell.guid.new(), - history,"Default Site", - '', - None, - None, - None, - None, - "ELEMENT", - None, - None, - None, - None, - None - )] - if sites: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'ProjectLink', - '', - project,sites - ) - if not buildings: - if ADD_DEFAULT_BUILDING: - if DEBUG: print("No building found. Adding default building") - buildings = [ifcfile.createIfcBuilding( - ifcopenshell.guid.new(), - history, - "Default Building", - '', - None, - None, - None, - None, - "ELEMENT", - None, - None, - None - )] - if buildings and (not sites): - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'ProjectLink', - '', - project,buildings - ) - if floors: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'BuildingLink', - '', - buildings[0],floors - ) - if sites and buildings: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'SiteLink', - '', - sites[0], - buildings - ) - - # treat objects that are not related to any site, building or storey - - untreated = [] - for k,v in products.items(): - if not(k in treated): - if (not buildings) or (k != buildings[0].Name): - if not(Draft.getType(FreeCAD.ActiveDocument.getObject(k)) in ["Site","Building","Floor","BuildingPart"]): - untreated.append(v) - elif Draft.getType(FreeCAD.ActiveDocument.getObject(k)) == "BuildingPart": - if not(FreeCAD.ActiveDocument.getObject(k).IfcType in ["Building","Building Storey","Site","Space","Undefined"]): - untreated.append(v) - if untreated: - if not defaulthost: - if ADD_DEFAULT_STOREY: - defaulthost = ifcfile.createIfcBuildingStorey( - ifcopenshell.guid.new(), - history, - "Default Storey", - '', - None, - None, - None, - None, - "ELEMENT", - None - ) - # if ADD_DEFAULT_STOREY is on, we need a building to host it, regardless of ADD_DEFAULT_BUILDING - if not buildings: - if DEBUG: print("No building found. Adding default building") - buildings = [ifcfile.createIfcBuilding( - ifcopenshell.guid.new(), - history, - "Default Building", - '', - None, - None, - None, - None, - "ELEMENT", - None, - None, - None - )] - if sites: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'SiteLink', - '', - sites[0], - buildings - ) - else: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'ProjectLink', - '', - project,buildings - ) - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'DefaultStoreyLink', - '', - buildings[0], - [defaulthost] - ) - elif buildings: - defaulthost = buildings[0] - if defaulthost: - ifcfile.createIfcRelContainedInSpatialStructure( - ifcopenshell.guid.new(), - history, - 'UnassignedObjectsLink', - '', - untreated, - defaulthost - ) - else: - # no default host: aggregate unassigned objects directly under the IfcProject - WARNING: NON STANDARD - if DEBUG: print("WARNING - Default building generation is disabled. You are producing a non-standard file.") - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'ProjectLink', - '', - project,untreated - ) - - # materials - - materials = {} - for m in Arch.getDocumentMaterials(): - relobjs = [] - for o in m.InList: - if hasattr(o,"Material"): - if o.Material: - if o.Material.isDerivedFrom("App::MaterialObject"): - # TODO : support multimaterials too - if o.Material.Name == m.Name: - if o.Name in products: - relobjs.append(products[o.Name]) - elif o.Name in subproducts: - relobjs.append(subproducts[o.Name]) - if relobjs: - l = m.Label - if six.PY2: - l = l.encode("utf8") - mat = ifcfile.createIfcMaterial(l) - materials[m.Label] = mat - rgb = None - if hasattr(m,"Color"): - rgb = m.Color[:3] - else: - for colorslot in ["Color","DiffuseColor","ViewColor"]: - if colorslot in m.Material: - if m.Material[colorslot]: - if m.Material[colorslot][0] == "(": - rgb = tuple([float(f) for f in m.Material[colorslot].strip("()").split(",")]) - break - if rgb: - psa = ifcbin.createIfcPresentationStyleAssignment(l,rgb[0],rgb[1],rgb[2]) - isi = ifcfile.createIfcStyledItem(None,[psa],None) - isr = ifcfile.createIfcStyledRepresentation(context,"Style","Material",[isi]) - imd = ifcfile.createIfcMaterialDefinitionRepresentation(None,None,[isr],mat) - ifcfile.createIfcRelAssociatesMaterial( - ifcopenshell.guid.new(), - history, - 'MaterialLink', - '', - relobjs, - mat - ) - - # 2D objects - - annos = {} - if EXPORT_2D: - curvestyles = {} - if annotations and DEBUG: print("exporting 2D objects...") - for anno in annotations: - 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) - if anno.isDerivedFrom("Part::Feature"): - reps = [] - sh = anno.Shape.copy() - sh.scale(0.001) # 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"): - l = FreeCAD.Vector(anno.Position).multiply(0.001) - pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) - tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) - s = ";".join(anno.LabelText) - if six.PY2: - s = s.encode("utf8") - txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT") - reps = [txt] - elif Draft.getType(anno) == "DraftText": - l = FreeCAD.Vector(anno.Placement.Base).multiply(0.001) - pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) - tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) - s = ";".join(anno.Text) - if six.PY2: - s = s.encode("utf8") - txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT") - reps = [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 - if six.PY2: - l = l.encode("utf8") - ann = ifcfile.createIfcAnnotation( - ifcopenshell.guid.new(), - history,l, - '', - None, - gpl, - rep - ) - annos[anno.Name] = ann - - # groups - - sortedgroups = [] - swallowed = [] - while groups: - for g in groups.keys(): - okay = True - for c in groups[g]: - if Draft.getType(FreeCAD.ActiveDocument.getObject(c)) in ["Group","VisGroup"]: - okay = False - for s in sortedgroups: - if s[0] == c: - okay = True - if okay: - sortedgroups.append([g,groups[g]]) - for g in sortedgroups: - if g[0] in groups.keys(): - del groups[g[0]] - #print("sorted groups:",sortedgroups) - containers = {} - for g in sortedgroups: - if g[1]: - children = [] - for o in g[1]: - if o in products.keys(): - children.append(products[o]) - elif o in annos.keys(): - children.append(annos[o]) - swallowed.append(annos[o]) - if children: - name = FreeCAD.ActiveDocument.getObject(g[0]).Label - if six.PY2: - name = name.encode("utf8") - grp = ifcfile.createIfcGroup( - ifcopenshell.guid.new(), - history, - name, - '', - None - ) - products[g[0]] = grp - spatialelements[g[0]] = grp - ass = ifcfile.createIfcRelAssignsToGroup( - ifcopenshell.guid.new(), - history, - 'GroupLink', - '', - children, - None, - grp - ) - - # stack groups inside containers - - stack = {} - for g in sortedgroups: - go = FreeCAD.ActiveDocument.getObject(g[0]) - for parent in go.InList: - if hasattr(parent,"Group") and (go in parent.Group): - if (parent.Name in spatialelements) and (g[0] in spatialelements): - stack.setdefault(parent.Name,[]).append(spatialelements[g[0]]) - for k,v in stack.items(): - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'GroupStackLink', - '', - spatialelements[k], - v - ) - - # add remaining 2D objects to default host - - if annos: - remaining = [anno for anno in annos.values() if anno not in swallowed] - if remaining: - if not defaulthost: - if ADD_DEFAULT_STOREY: - defaulthost = ifcfile.createIfcBuildingStorey( - ifcopenshell.guid.new(), - history, - "Default Storey", - '', - None, - None, - None, - None, - "ELEMENT", - None - ) - # if ADD_DEFAULT_STOREY is on, we need a building to host it, regardless of ADD_DEFAULT_BUILDING - if not buildings: - buildings = [ifcfile.createIfcBuilding( - ifcopenshell.guid.new(), - history, - "Default Building", - '', - None, - None, - None, - None, - "ELEMENT", - None, - None, - None - )] - if sites: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'SiteLink', - '', - sites[0], - buildings - ) - else: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'ProjectLink', - '', - project,buildings - ) - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'DefaultStoreyLink', - '', - buildings[0], - [defaulthost] - ) - elif ADD_DEFAULT_BUILDING: - if not buildings: - defaulthost = ifcfile.createIfcBuilding( - ifcopenshell.guid.new(), - history, - "Default Building", - '', - None, - None, - None, - None, - "ELEMENT", - None, - None, - None - ) - if sites: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'SiteLink', - '', - sites[0], - [defaulthost] - ) - else: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'ProjectLink', - '', - project, - [defaulthost] - ) - if defaulthost: - ifcfile.createIfcRelContainedInSpatialStructure( - ifcopenshell.guid.new(), - history, - 'AnnotationsLink', - '', - remaining, - defaulthost - ) - else: - ifcfile.createIfcRelAggregates( - ifcopenshell.guid.new(), - history, - 'ProjectLink', - '', - project, - remaining - ) - - if DEBUG: print("writing ",filename,"...") - - filename = decode(filename) - - ifcfile.write(filename) - - if STORE_UID: - # some properties might have been changed - FreeCAD.ActiveDocument.recompute() - - os.remove(templatefile) - - if DEBUG and ifcbin.compress: - f = pyopen(filename,"r") - s = len(f.read().split("\n")) - f.close() - print("Compression ratio:",int((float(ifcbin.spared)/(s+ifcbin.spared))*100),"%") - del ifcbin - - -# ************************************************************************************************ -# ********** helper for export IFC ************** - -def getPropertyData(key,value): - - # in 0.18, properties in IfcProperties dict are stored as "key":"pset;;type;;value" or "key":"type;;value" - # in 0.19, key = name;;pset, value = ptype;;value (because there can be several props with same name) - - pset = None - pname = key - if ";;" in pname: - pname = key.split(";;")[0] - pset = key.split(";;")[-1] - value = value.split(";;") - if len(value) == 3: - pset = value[0] - ptype = value[1] - pvalue = value[2] - elif len(value) == 2: - if not pset: - pset = "Default property set" - ptype = value[0] - pvalue = value[1] - else: - if DEBUG:print(" unable to export property:",pname,value) - return pset, pname, ptype, None - - #if DEBUG: print(" property ",pname," : ",pvalue.encode("utf8"), " (", str(ptype), ") in ",pset) - if ptype in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']: - if six.PY2: - pvalue = pvalue.encode("utf8") - elif ptype == "IfcBoolean": - if pvalue == ".T.": - pvalue = True - else: - pvalue = False - elif ptype == "IfcLogical": - if pvalue.upper() == "TRUE": - pvalue = True - else: - pvalue = False - elif ptype == "IfcInteger": - pvalue = int(pvalue) - else: - try: - pvalue = float(pvalue) - except: - try: - pvalue = FreeCAD.Units.Quantity(pvalue).Value - except: - if six.PY2: - pvalue = pvalue.encode("utf8") - if DEBUG:print(" warning: unable to export property as numeric value:",pname,pvalue) - - # print('pset: {}, pname: {}, ptype: {}, pvalue: {}'.format(pset, pname, ptype, pvalue)) - return pset, pname, ptype, pvalue - - -def isStandardCase(obj,ifctype): - - if ifctype.endswith("StandardCase"): - return False # type is already standard case, return False so "StandardCase" is not added twice - if hasattr(obj,"Proxy") and hasattr(obj.Proxy,"isStandardCase"): - return obj.Proxy.isStandardCase(obj) - return False - - -def getIfcTypeFromObj(obj): - - if (Draft.getType(obj) == "BuildingPart") and hasattr(obj,"IfcType") and (obj.IfcType == "Undefined"): - ifctype = "IfcBuildingStorey" # export BuildingParts as Storeys if their type wasn't explicitly set - elif hasattr(obj,"IfcType"): - ifctype = obj.IfcType.replace(" ","") - else: - ifctype = Draft.getType(obj) - - if ifctype in translationtable.keys(): - ifctype = translationtable[ifctype] - if ifctype == "VisGroup": - ifctype = "Group" - - return "Ifc" + ifctype - - -def exportIFC2X3Attributes(obj, kwargs): - - ifctype = getIfcTypeFromObj(obj) - if ifctype in ["IfcSlab", "IfcFooting"]: - kwargs.update({"PredefinedType": "NOTDEFINED"}) - elif ifctype == "IfcBuilding": - kwargs.update({"CompositionType": "ELEMENT"}) - elif ifctype == "IfcBuildingStorey": - kwargs.update({"CompositionType": "ELEMENT"}) - elif ifctype == "IfcBuildingElementProxy": - kwargs.update({"CompositionType": "ELEMENT"}) - elif ifctype == "IfcSpace": - internal = "NOTDEFINED" - if hasattr(obj,"Internal"): - if obj.Internal: - internal = "INTERNAL" - else: - internal = "EXTERNAL" - kwargs.update({ - "CompositionType": "ELEMENT", - "InteriorOrExteriorSpace": internal, - "ElevationWithFlooring": obj.Shape.BoundBox.ZMin/1000.0 - }) - elif ifctype == "IfcReinforcingBar": - kwargs.update({ - "NominalDiameter": obj.Diameter.Value, - "BarLength": obj.Length.Value - }) - elif ifctype == "IfcBuildingStorey": - kwargs.update({"Elevation": obj.Placement.Base.z/1000.0}) - return kwargs - - -def exportIfcAttributes(obj, kwargs): - - for property in obj.PropertiesList: - if obj.getGroupOfProperty(property) == "IFC Attributes" and obj.getPropertyByName(property): - value = obj.getPropertyByName(property) - if isinstance(value, FreeCAD.Units.Quantity): - value = float(value) - if property in ["ElevationWithFlooring","Elevation"]: - value = value/1000 # some properties must be changed to meters - kwargs.update({property: value}) - return kwargs - - -def buildAddress(obj,ifcfile): - - a = obj.Address or None - p = obj.PostalCode or None - t = obj.City or None - r = obj.Region or None - c = obj.Country or None - if six.PY2: - if a: - a = a.encode("utf8") - if p: - p = p.encode("utf8") - if t: - t = t.encode("utf8") - if r: - r = r.encode("utf8") - if c: - c = c.encode("utf8") - if a or p or t or r or c: - addr = ifcfile.createIfcPostalAddress("SITE",'Site Address','',None,[a],None,t,r,p,c) - else: - addr = None - return addr - - def createFromProperties(propsets,ifcfile): "creates a FreeCAD parametric object from a set of properties" @@ -3002,528 +1576,6 @@ def createFromProperties(propsets,ifcfile): return obj -def createCurve(ifcfile,wire): - - "creates an IfcCompositeCurve from a shape" - - segments = [] - pol = None - last = None - if wire.ShapeType == "edge": - edges = [wire] - else: - edges = Part.__sortEdges__(wire.Edges) - for e in edges: - if isinstance(e.Curve,Part.Circle): - xaxis = e.Curve.XAxis - zaxis = e.Curve.Axis - follow = True - if last: - if not DraftVecUtils.equals(last,e.Vertexes[0].Point): - follow = False - last = e.Vertexes[0].Point - prev = e.Vertexes[-1].Point - else: - last = e.Vertexes[-1].Point - prev = e.Vertexes[0].Point - else: - last = e.Vertexes[-1].Point - prev = e.Vertexes[0].Point - p1 = math.degrees(-DraftVecUtils.angle(prev.sub(e.Curve.Center),xaxis,zaxis)) - p2 = math.degrees(-DraftVecUtils.angle(last.sub(e.Curve.Center),xaxis,zaxis)) - da = DraftVecUtils.angle(e.valueAt(e.FirstParameter+0.1).sub(e.Curve.Center),prev.sub(e.Curve.Center)) - #print("curve params:",p1,",",p2,"da=",da) - if p1 < 0: - p1 = 360 + p1 - if p2 < 0: - p2 = 360 + p2 - if da > 0: - #follow = not(follow) # now we always draw segments in the correct order, so follow is always true - pass - #print(" circle from",prev,"to",last,"a1=",p1,"a2=",p2) - ovc = ifcbin.createIfcCartesianPoint(tuple(e.Curve.Center)) - zvc = ifcbin.createIfcDirection(tuple(zaxis)) - xvc = ifcbin.createIfcDirection(tuple(xaxis)) - plc = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc) - cir = ifcfile.createIfcCircle(plc,e.Curve.Radius) - curve = ifcfile.createIfcTrimmedCurve( - cir, - [ifcfile.createIfcParameterValue(p1)], - [ifcfile.createIfcParameterValue(p2)], - follow, - "PARAMETER" - ) - else: - verts = [vertex.Point for vertex in e.Vertexes] - if last: - if not DraftVecUtils.equals(last,verts[0]): - verts.reverse() - last = e.Vertexes[0].Point - else: - last = e.Vertexes[-1].Point - else: - last = e.Vertexes[-1].Point - #print(" polyline:",verts) - pts = [ifcbin.createIfcCartesianPoint(tuple(v)) for v in verts] - curve = ifcbin.createIfcPolyline(pts) - segment = ifcfile.createIfcCompositeCurveSegment("CONTINUOUS",True,curve) - segments.append(segment) - if segments: - pol = ifcfile.createIfcCompositeCurve(segments,False) - return pol - - -def getEdgesAngle(edge1, edge2): - - """ getEdgesAngle(edge1, edge2): returns a angle between two edges.""" - - vec1 = vec(edge1) - vec2 = vec(edge2) - angle = vec1.getAngle(vec2) - angle = math.degrees(angle) - return angle - - -def checkRectangle(edges): - - """ checkRectangle(edges=[]): This function checks whether the given form is a rectangle - or not. It will return True when edges form a rectangular shape or return False - when edges do not form a rectangular shape.""" - - if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("DisableIfcRectangleProfileDef",False): - return False - if len(edges) != 4: - return False - angles = [ - round(getEdgesAngle(edges[0], edges[1])), - round(getEdgesAngle(edges[0], edges[2])), - round(getEdgesAngle(edges[0], edges[3])) - ] - if angles.count(90) == 2 and (angles.count(180) == 1 or angles.count(0) == 1): - return True - return False - - -def getProfile(ifcfile,p): - - """returns an IFC profile definition from a shape""" - - import Part - import DraftGeomUtils - profile = None - if len(p.Edges) == 1: - pxvc = ifcbin.createIfcDirection((1.0,0.0)) - povc = ifcbin.createIfcCartesianPoint((0.0,0.0)) - pt = ifcbin.createIfcAxis2Placement2D(povc,pxvc) - if isinstance(p.Edges[0].Curve,Part.Circle): - # extruded circle - profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt,p.Edges[0].Curve.Radius) - elif isinstance(p.Edges[0].Curve,Part.Ellipse): - # extruded ellipse - profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt,p.Edges[0].Curve.MajorRadius,p.Edges[0].Curve.MinorRadius) - elif (checkRectangle(p.Edges)): - # arbitrarily use the first edge as the rectangle orientation - d = vec(p.Edges[0]) - d.normalize() - pxvc = ifcbin.createIfcDirection(tuple(d)) - povc = ifcbin.createIfcCartesianPoint(tuple(p.CenterOfMass)) - pt = ifcbin.createIfcAxis2Placement2D(povc,pxvc) - #semiPerimeter = p.Length/2 - #diff = math.sqrt(semiPerimeter**2 - 4*p.Area) - #b = max(abs((semiPerimeter + diff)/2),abs((semiPerimeter - diff)/2)) - #h = min(abs((semiPerimeter + diff)/2),abs((semiPerimeter - diff)/2)) - b = p.Edges[0].Length - h = p.Edges[1].Length - profile = ifcfile.createIfcRectangleProfileDef("AREA",'rectangular',pt,b,h) - elif (len(p.Faces) == 1) and (len(p.Wires) > 1): - # face with holes - f = p.Faces[0] - if DraftGeomUtils.hasCurves(f.OuterWire): - outerwire = createCurve(ifcfile,f.OuterWire) - else: - w = Part.Wire(Part.__sortEdges__(f.OuterWire.Edges)) - pts = [ifcbin.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] - outerwire = ifcbin.createIfcPolyline(pts) - innerwires = [] - for w in f.Wires: - if w.hashCode() != f.OuterWire.hashCode(): - if DraftGeomUtils.hasCurves(w): - innerwires.append(createCurve(ifcfile,w)) - else: - w = Part.Wire(Part.__sortEdges__(w.Edges)) - pts = [ifcbin.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] - innerwires.append(ifcbin.createIfcPolyline(pts)) - profile = ifcfile.createIfcArbitraryProfileDefWithVoids("AREA",None,outerwire,innerwires) - else: - if DraftGeomUtils.hasCurves(p): - # extruded composite curve - pol = createCurve(ifcfile,p) - else: - # extruded polyline - w = Part.Wire(Part.__sortEdges__(p.Wires[0].Edges)) - pts = [ifcbin.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] - pol = ifcbin.createIfcPolyline(pts) - profile = ifcfile.createIfcArbitraryClosedProfileDef("AREA",None,pol) - return profile - - -def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None): - - """returns an IfcShapeRepresentation object or None""" - - import Part - import DraftGeomUtils - import DraftVecUtils - shapes = [] - placement = None - productdef = None - shapetype = "no shape" - tostore = False - subplacement = None - - # check for clones - - if (not subtraction) and (not forcebrep): - 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() - 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(0.001))) - transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3) - mapitem = ifcfile.createIfcMappedItem(repmap,transf) - shapes = [mapitem] - solidType = "MappedRepresentation" - shapetype = "clone" - else: - # base shape not yet created - tostore = k - - # unhandled case: object is duplicated because of Axis - 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): - profile = None - ev = FreeCAD.Vector() - if hasattr(obj,"Proxy"): - if hasattr(obj.Proxy,"getRebarData"): - # export rebars as IfcSweptDiskSolid - rdata = obj.Proxy.getRebarData(obj) - if rdata: - # convert to meters - r = rdata[1] * 0.001 - for w in rdata[0]: - w.scale(0.001) - cur = createCurve(ifcfile,w) - shape = ifcfile.createIfcSweptDiskSolid(cur,r) - shapes.append(shape) - solidType = "SweptSolid" - shapetype = "extrusion" - if (not shapes) and hasattr(obj.Proxy,"getExtrusionData"): - extdata = obj.Proxy.getExtrusionData(obj) - if extdata: - #print(extdata) - # convert to meters - p = extdata[0] - if not isinstance(p,list): - p = [p] - ev = extdata[1] - if not isinstance(ev,list): - ev = [ev] - pl = extdata[2] - if not isinstance(pl,list): - pl = [pl] - for i in range(len(p)): - pi = p[i] - pi.scale(0.001) - if i < len(ev): - evi = FreeCAD.Vector(ev[i]) - else: - evi = FreeCAD.Vector(ev[-1]) - evi.multiply(0.001) - if i < len(pl): - pli = pl[i].copy() - else: - pli = pl[-1].copy() - pli.Base = pli.Base.multiply(0.001) - pstr = str([v.Point for v in p[i].Vertexes]) - if pstr in profiledefs: - profile = profiledefs[pstr] - shapetype = "reusing profile" - else: - profile = getProfile(ifcfile,pi) - if profile: - profiledefs[pstr] = profile - if profile and not(DraftVecUtils.isNull(evi)): - #ev = pl.Rotation.inverted().multVec(evi) - #print("evi:",evi) - if not tostore: - # add the object placement to the profile placement. Otherwise it'll be done later at map insert - pl2 = obj.getGlobalPlacement() - pl2.Base = pl2.Base.multiply(0.001) - pli = pl2.multiply(pli) - xvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(1,0,0)))) - zvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(0,0,1)))) - ovc = ifcbin.createIfcCartesianPoint(tuple(pli.Base)) - lpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc) - edir = ifcbin.createIfcDirection(tuple(FreeCAD.Vector(evi).normalize())) - shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,evi.Length) - shapes.append(shape) - solidType = "SweptSolid" - shapetype = "extrusion" - - if not shapes: - - # check if we keep a null shape (additions-only object) - - if (hasattr(obj,"Base") and hasattr(obj,"Width") and hasattr(obj,"Height")) \ - and (not obj.Base) \ - and obj.Additions \ - and (not obj.Width.Value) \ - and (not obj.Height.Value): - shapes = None - - else: - - # brep representation - - fcshape = None - solidType = "Brep" - if subtraction: - if hasattr(obj,"Proxy"): - if hasattr(obj.Proxy,"getSubVolume"): - fcshape = obj.Proxy.getSubVolume(obj) - if not fcshape: - if obj.isDerivedFrom("Part::Feature"): - #if hasattr(obj,"Base") and hasattr(obj,"Additions")and hasattr(obj,"Subtractions"): - if False: # above is buggy. No way to duplicate shapes that way? - if obj.Base and (not obj.Additions) and not(obj.Subtractions): - if obj.Base.isDerivedFrom("Part::Feature"): - if obj.Base.Shape: - if obj.Base.Shape.Solids: - fcshape = obj.Base.Shape - subplacement = FreeCAD.Placement(obj.Placement) - if not fcshape: - if obj.Shape: - if not obj.Shape.isNull(): - fcshape = obj.Shape.copy() - fcshape.Placement = obj.getGlobalPlacement() - if fcshape: - shapedef = str([v.Point for v in fcshape.Vertexes]) - if shapedef in shapedefs: - shapes = shapedefs[shapedef] - shapetype = "reusing brep" - else: - - # new ifcopenshell serializer - - from ifcopenshell import geom - serialized = False - if hasattr(geom,"serialise") and obj.isDerivedFrom("Part::Feature") and SERIALIZE: - if obj.Shape.Faces: - sh = obj.Shape.copy() - sh.Placement = obj.getGlobalPlacement() - sh.scale(0.001) # to meters - p = geom.serialise(sh.exportBrepToString()) - if p: - productdef = ifcfile.add(p) - for rep in productdef.Representations: - rep.ContextOfItems = context - 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) - shapetype = "advancedbrep" - shapes = None - serialized = True - - if not serialized: - - # old method - - solids = [] - - # if this is a clone, place back the shape in null position - if tostore: - fcshape.Placement = FreeCAD.Placement() - - if fcshape.Solids: - dataset = fcshape.Solids - else: - dataset = fcshape.Shells - #if DEBUG: print("Warning! object contains no solids") - - for fcsolid in dataset: - fcsolid.scale(0.001) # to meters - faces = [] - curves = False - shapetype = "brep" - for fcface in fcsolid.Faces: - for e in fcface.Edges: - if DraftGeomUtils.geomType(e) != "Line": - from FreeCAD import Base - try: - if e.curvatureAt(e.FirstParameter+(e.LastParameter-e.FirstParameter)/2) > 0.0001: - curves = True - break - except Part.OCCError: - pass - except Base.FreeCADError: - pass - if curves: - joinfacets = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcJoinCoplanarFacets",False) - usedae = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcUseDaeOptions",False) - if joinfacets: - result = Arch.removeCurves(fcsolid,dae=usedae) - if result: - fcsolid = result - else: - # fall back to standard triangulation - joinfacets = False - if not joinfacets: - shapetype = "triangulated" - if usedae: - import importDAE - tris = importDAE.triangulate(fcsolid) - else: - tris = fcsolid.tessellate(tessellation) - for tri in tris[1]: - pts = [ifcbin.createIfcCartesianPoint(tuple(tris[0][i])) for i in tri] - loop = ifcbin.createIfcPolyLoop(pts) - bound = ifcfile.createIfcFaceOuterBound(loop,True) - face = ifcfile.createIfcFace([bound]) - faces.append(face) - fcsolid = Part.Shape() # empty shape so below code is not executed - - for fcface in fcsolid.Faces: - loops = [] - verts = [v.Point for v in fcface.OuterWire.OrderedVertexes] - c = fcface.CenterOfMass - v1 = verts[0].sub(c) - v2 = verts[1].sub(c) - try: - n = fcface.normalAt(0,0) - except Part.OCCError: - continue # this is a very wrong face, it probably shouldn't be here... - if DraftVecUtils.angle(v2,v1,n) >= 0: - verts.reverse() # inverting verts order if the direction is couterclockwise - pts = [ifcbin.createIfcCartesianPoint(tuple(v)) for v in verts] - loop = ifcbin.createIfcPolyLoop(pts) - bound = ifcfile.createIfcFaceOuterBound(loop,True) - loops.append(bound) - for wire in fcface.Wires: - if wire.hashCode() != fcface.OuterWire.hashCode(): - verts = [v.Point for v in wire.OrderedVertexes] - if len(verts) > 1: - v1 = verts[0].sub(c) - v2 = verts[1].sub(c) - if DraftVecUtils.angle(v2,v1,DraftVecUtils.neg(n)) >= 0: - verts.reverse() - pts = [ifcbin.createIfcCartesianPoint(tuple(v)) for v in verts] - loop = ifcbin.createIfcPolyLoop(pts) - bound = ifcfile.createIfcFaceBound(loop,True) - loops.append(bound) - else: - print("Warning: wire with one/no vertex in ", obj.Label) - face = ifcfile.createIfcFace(loops) - faces.append(face) - - if faces: - shell = ifcfile.createIfcClosedShell(faces) - shape = ifcfile.createIfcFacetedBrep(shell) - shapes.append(shape) - - shapedefs[shapedef] = shapes - - if shapes: - - colorshapes = shapes # to keep track of individual shapes for coloring below - if tostore: - subrep = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes) - 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) - repmap = ifcfile.createIfcRepresentationMap(gpl,subrep) - pla = obj.getGlobalPlacement() - axis1 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0)))) - axis2 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0)))) - origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(0.001))) - axis3 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1)))) - transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3) - mapitem = ifcfile.createIfcMappedItem(repmap,transf) - shapes = [mapitem] - sharedobjects[tostore] = repmap - solidType = "MappedRepresentation" - - # set surface style - - shapecolor = None - diffusecolor = None - transparency = 0.0 - if colors: - # color dict is given - if obj.Name in colors: - color = colors[obj.Name] - shapecolor = color - if isinstance(color[0],tuple): - # this is a diffusecolor. For now, use the first color - #TODO: Support per-face colors - diffusecolor = color - shapecolor = color[0] - elif FreeCAD.GuiUp and (not subtraction) and hasattr(obj.ViewObject,"ShapeColor"): - # every object gets a surface style. If the obj has a material, the surfstyle - # is named after it. Revit will treat surfacestyles as materials (and discard - # actual ifcmaterial) - shapecolor = obj.ViewObject.ShapeColor[:3] - transparency = obj.ViewObject.Transparency/100.0 - if hasattr(obj.ViewObject,"DiffuseColor"): - diffusecolor = obj.ViewObject.DiffuseColor - if shapecolor and (shapetype != "clone"): # cloned objects are already colored - key = None - rgbt = [shapecolor+(transparency,) for shape in shapes] - if diffusecolor \ - and (len(diffusecolor) == len(obj.Shape.Faces)) \ - and (len(obj.Shape.Solids) == len(colorshapes)): - i = 0 - rgbt = [] - for sol in obj.Shape.Solids: - rgbt.append(diffusecolor[i]) - i += len(sol.Faces) - for i,shape in enumerate(colorshapes): - key = rgbt[i] - #if hasattr(obj,"Material"): - # if obj.Material: - # key = obj.Material.Name #TODO handle multimaterials - if key in surfstyles: - psa = surfstyles[key] - else: - m = None - if hasattr(obj,"Material"): - if obj.Material: - m = obj.Material.Label - if six.PY2: - m = m.encode("utf8") - psa = ifcbin.createIfcPresentationStyleAssignment(m,rgbt[i][0],rgbt[i][1],rgbt[i][2],rgbt[i][3]) - surfstyles[key] = psa - isi = ifcfile.createIfcStyledItem(shape,[psa],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) - representation = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes) - productdef = ifcfile.createIfcProductDefinitionShape(None,None,[representation]) - - return productdef,placement,shapetype - - # Below are 2D helper functions needed while IfcOpenShell cannot do this itself...