From b172c234a8b452671d74c31500b77c4e1064ab4e Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Fri, 10 Aug 2018 18:37:13 -0300 Subject: [PATCH] Arch: Initial support to export quantities to IFC --- src/Mod/Arch/importIFC.py | 74 ++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index 00ec2bde91..56ca90b0e4 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -114,9 +114,9 @@ END-ISO-10303-21; def decode(filename,utf=False): - + "turns unicodes into strings" - + if isinstance(filename,unicode): # workaround since ifcopenshell currently can't handle unicode filenames if utf: @@ -129,9 +129,9 @@ def decode(filename,utf=False): def doubleClickTree(item,column): - + "a double-click callback function for the IFC explorer tool" - + txt = item.text(column) if "Entity #" in txt: eid = txt.split("#")[1].split(":")[0] @@ -142,13 +142,13 @@ def doubleClickTree(item,column): def dd2dms(dd): - + "converts decimal degrees to degrees,minutes,seconds" dd = abs(dd) minutes,seconds = divmod(dd*3600,60) degrees,minutes = divmod(minutes,60) - if dd < 0: + if dd < 0: degrees = -degrees return (int(degrees),int(minutes),int(seconds)) @@ -161,9 +161,9 @@ def dms2dd(degrees, minutes, seconds, milliseconds=0): 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 @@ -199,7 +199,7 @@ def getPreferences(): def explore(filename=None): - + """explore([filename]): opens a dialog showing the contents of an IFC file. If no filename is given, a dialog will pop up to choose a file.""" @@ -359,7 +359,7 @@ def explore(filename=None): def open(filename,skip=[],only=[],root=None): - + "opens an IFC file in a new document" docname = os.path.splitext(os.path.basename(filename))[0] @@ -371,7 +371,7 @@ def open(filename,skip=[],only=[],root=None): def insert(filename,docname,skip=[],only=[],root=None): - + """insert(filename,docname,skip=[],only=[],root=None): imports the contents of an IFC file. skip can contain a list of ids of objects to be skipped, only can restrict the import to certain object ids (will also get their children) and root can be used to @@ -395,15 +395,15 @@ def insert(filename,docname,skip=[],only=[],root=None): if DEBUG: print("done.") global ROOT_ELEMENT, parametrics - + if root: ROOT_ELEMENT = root #global ifcfile # keeping global for debugging purposes - + filename = decode(filename,utf=True) ifcfile = ifcopenshell.open(filename) - + # set default ifcopenshell optionss to work in brep mode from ifcopenshell import geom settings = ifcopenshell.geom.settings() @@ -414,7 +414,7 @@ def insert(filename,docname,skip=[],only=[],root=None): settings.set(settings.DISABLE_OPENING_SUBTRACTIONS,True) if SPLIT_LAYERS and hasattr(settings,"APPLY_LAYERSETS"): settings.set(settings.APPLY_LAYERSETS,True) - + # gather easy entity types sites = ifcfile.by_type("IfcSite") buildings = ifcfile.by_type("IfcBuilding") @@ -441,7 +441,7 @@ def insert(filename,docname,skip=[],only=[],root=None): sharedobjects = {} # { representationmapid:object } parametrics = [] # a list of imported objects whose parametric relationships need processing after all objects have been created profiles = {} # to store reused extrusion profiles {ifcid:fcobj,...} - + for r in ifcfile.by_type("IfcRelContainedInSpatialStructure"): additions.setdefault(r.RelatingStructure.id(),[]).extend([e.id() for e in r.RelatedElements]) for r in ifcfile.by_type("IfcRelAggregates"): @@ -511,7 +511,7 @@ def insert(filename,docname,skip=[],only=[],root=None): products = tp # only import a list of IDs and their children - if only: + if only: ids = [] while only: currentid = only.pop() @@ -609,7 +609,7 @@ def insert(filename,docname,skip=[],only=[],root=None): else: sharedobjects[bid] = None store = bid - + # additional setting for structural entities if hasattr(settings,"INCLUDE_CURVES"): if structobj: @@ -660,7 +660,7 @@ def insert(filename,docname,skip=[],only=[],root=None): if DEBUG: print(shape.Solids," ",end="") baseobj = shape else: - + # create base shape object if clone: if DEBUG: print("clone ",end="") @@ -963,9 +963,9 @@ def insert(filename,docname,skip=[],only=[],root=None): if r.RepresentationIdentifier == "FootPrint": annotations.append(product) break - + # additional properties for specific types - + if product.is_a("IfcSite"): if product.RefElevation: obj.Elevation = product.RefElevation*1000 @@ -1283,7 +1283,7 @@ def insert(filename,docname,skip=[],only=[],root=None): class recycler: "the compression engine - a mechanism to reuse ifc entities if needed" - + # this object has some methods identical to corresponding ifcopenshell methods, # but it checks if a similar entity already exists before creating a new one # to compress a new type, just add the necessary method here @@ -1711,7 +1711,7 @@ def export(exportList,filename): prod2 = ifcfile.createIfcBuildingElementProxy(ifcopenshell.guid.compress(uuid.uuid1().hex),history,o.Label.encode("utf8"),None,None,p2,r2,None,"ELEMENT") subproducts[o.Name] = prod2 ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'Addition','',product,[prod2]) - + # subtractions @@ -1882,6 +1882,26 @@ def export(exportList,filename): #if DEBUG : print("no ifc properties to export") pass + # Quantities + + if hasattr(obj,"IfcAttributes"): + quantities = [] + if ("ExportHeight" in obj.IfcAttributes) and obj.IfcAttributes["ExportHeight"] and hasattr(obj,"Height"): + quantities.append(ifcfile.createIfcQuantityLength('Height',None,None,obj.Height.Value/1000.0)) + if ("ExportWidth" in obj.IfcAttributes) and obj.IfcAttributes["ExportWidth"] and hasattr(obj,"Width"): + quantities.append(ifcfile.createIfcQuantityLength('Width',None,None,obj.Width.Value/1000.0)) + if ("ExportLength" in obj.IfcAttributes) and obj.IfcAttributes["ExportLength"] and hasattr(obj,"Length"): + quantities.append(ifcfile.createIfcQuantityLength('Length',None,None,obj.Length.Value/1000.0)) + if ("ExportHorizontalArea" in obj.IfcAttributes) and obj.IfcAttributes["ExportHorizontalArea"] and hasattr(obj,"HorizontalArea"): + quantities.append(ifcfile.createIfcQuantityArea('HorizontalArea',None,None,obj.HorizontalArea.Value/1000000.0)) + if ("ExportVerticalArea" in obj.IfcAttributes) and obj.IfcAttributes["ExportVerticalArea"] and hasattr(obj,"VerticalArea"): + quantities.append(ifcfile.createIfcQuantityArea('VerticalArea',None,None,obj.VerticalArea.Value/1000000.0)) + if ("ExportVolume" in obj.IfcAttributes) and obj.IfcAttributes["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.compress(uuid.uuid1().hex),history,"ElementQuantities",None,"FreeCAD",quantities) + ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],eltq) + if FULL_PARAMETRIC: # exporting all the object properties FreeCADProps = [] @@ -1960,6 +1980,7 @@ def export(exportList,filename): 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 not bp.IfcRole in ["Site","Building","Building Storey","Space","Undefined"]: if bp.Name in products: @@ -1972,6 +1993,7 @@ def export(exportList,filename): ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'Assembly','',products[bp.Name],subs) # floors/buildingparts + for floor in Draft.getObjectsOfType(objectslist,"Floor")+Draft.getObjectsOfType(objectslist,"BuildingPart"): if (Draft.getType(floor) == "Floor") or (hasattr(floor,"IfcRole") and floor.IfcRole == "Building Storey"): objs = Draft.getGroupContents(floor,walls=True,addgroups=True) @@ -1990,6 +2012,7 @@ def export(exportList,filename): defaulthost = f # buildings + for building in Draft.getObjectsOfType(objectslist,"Building")+Draft.getObjectsOfType(objectslist,"BuildingPart"): if (Draft.getType(building) == "Building") or (hasattr(building,"IfcRole") and building.IfcRole == "Building"): objs = Draft.getGroupContents(building,walls=True,addgroups=True) @@ -2016,6 +2039,7 @@ def export(exportList,filename): defaulthost = b # sites + for site in Draft.getObjectsOfType(objectslist,"Site"): objs = Draft.getGroupContents(site,walls=True,addgroups=True) objs = Arch.pruneIncluded(objs) @@ -2209,8 +2233,8 @@ def export(exportList,filename): def buildAddress(obj,ifcfile): - - + + a = obj.Address.encode("utf8") or None p = obj.PostalCode.encode("utf8") or None t = obj.City.encode("utf8") or None