diff --git a/src/Mod/Arch/Resources/ui/preferences-ifc.ui b/src/Mod/Arch/Resources/ui/preferences-ifc.ui index b3ab253541..28835df005 100644 --- a/src/Mod/Arch/Resources/ui/preferences-ifc.ui +++ b/src/Mod/Arch/Resources/ui/preferences-ifc.ui @@ -7,7 +7,7 @@ 0 0 463 - 910 + 937 @@ -634,6 +634,40 @@ A building storey is not mandatory but a common practice to have at least one in + + + + + + IFC file units + + + + + + + The units you want your IFC file to be exported to. Note that IFC file are ALWAYS written in metric units. Imperial units are only a conversion applied on top of it. But some BIM applications will use this to choose which unit to work with when opening the file. + + + ifcUnit + + + Mod/Arch + + + + Metric + + + + + Imperial + + + + + + diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index 3f19602df9..69ce88e7fb 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -99,7 +99,6 @@ DATA; #16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); #17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); #18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); -#19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); ENDSEC; END-ISO-10303-21; """ @@ -116,6 +115,16 @@ def getPreferences(): if FreeCAD.GuiUp and p.GetBool("ifcShowDialog",False): import FreeCADGui FreeCADGui.showPreferences("Import-Export",0) + ifcunit = p.GetInt("ifcUnit",0) + f = 0.001 + u = "metre" + if ifcunit == 1: + f = 0.00328084 + u = "foot" + #if ifcunit == "inch": + # f = 0.03937008 + # not yet implemented, and I don't even know if it is interesting to do it. + # the only real use of these units is to make revit choose which mode to work with preferences = { 'DEBUG': p.GetBool("ifcDebug",False), @@ -127,7 +136,9 @@ def getPreferences(): 'FULL_PARAMETRIC': p.GetBool("IfcExportFreeCADProperties",False), 'ADD_DEFAULT_SITE': p.GetBool("IfcAddDefaultSite",False), 'ADD_DEFAULT_STOREY': p.GetBool("IfcAddDefaultStorey",False), - 'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding",True) + 'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding",True), + 'IFC_UNIT': u, + 'SCALE_FACTOR': f } return preferences @@ -188,6 +199,7 @@ def export(exportList,filename,colors=None,preferences=None): os.close(templatefilehandle) global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs ifcfile = ifcopenshell.open(templatefile) + ifcfile = exportIFCHelper.writeUnits(ifcfile,preferences["IFC_UNIT"]) history = ifcfile.by_type("IfcOwnerHistory")[0] objectslist = Draft.getGroupContents(exportList,walls=True,addgroups=True) annotations = [] @@ -307,8 +319,8 @@ def export(exportList,filename,colors=None,preferences=None): for axg in axgroups: ifcaxg = [] for ax in axg: - p1 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[0]).multiply(0.001)[:2])) - p2 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[1]).multiply(0.001)[:2])) + p1 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[0]).multiply(preferences['SCALE_FACTOR'])[:2])) + p2 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[1]).multiply(preferences['SCALE_FACTOR'])[:2])) pol = ifcbin.createIfcPolyline([p1,p2]) ifcpols.append(pol) axis = ifcfile.createIfcGridAxis(ax[2],pol,True) @@ -383,14 +395,14 @@ def export(exportList,filename,colors=None,preferences=None): kwargs.update({ "RefLatitude":dd2dms(obj.Latitude), "RefLongitude":dd2dms(obj.Longitude), - "RefElevation":obj.Elevation.Value/1000.0, + "RefElevation":obj.Elevation.Value*preferences['SCALE_FACTOR'], "SiteAddress":buildAddress(obj,ifcfile), "CompositionType": "ELEMENT" }) if schema == "IFC2X3": - kwargs = exportIFC2X3Attributes(obj, kwargs) + kwargs = exportIFC2X3Attributes(obj, kwargs, preferences['SCALE_FACTOR']) else: - kwargs = exportIfcAttributes(obj, kwargs) + kwargs = exportIfcAttributes(obj, kwargs, preferences['SCALE_FACTOR']) # creating the product @@ -639,17 +651,17 @@ def export(exportList,filename,colors=None,preferences=None): 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)) + quantities.append(ifcfile.createIfcQuantityLength('Height',None,None,obj.Height.Value*preferences['SCALE_FACTOR'])) 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)) + quantities.append(ifcfile.createIfcQuantityLength('Width',None,None,obj.Width.Value*preferences['SCALE_FACTOR'])) 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)) + quantities.append(ifcfile.createIfcQuantityLength('Length',None,None,obj.Length.Value*preferences['SCALE_FACTOR'])) 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)) + quantities.append(ifcfile.createIfcQuantityArea('HorizontalArea',None,None,obj.HorizontalArea.Value*(preferences['SCALE_FACTOR']**2))) 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)) + quantities.append(ifcfile.createIfcQuantityArea('VerticalArea',None,None,obj.VerticalArea.Value*(preferences['SCALE_FACTOR']**2))) 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)) + quantities.append(ifcfile.createIfcQuantityVolume('Volume',None,None,obj.Shape.Volume*(preferences['SCALE_FACTOR']**3))) if quantities: eltq = ifcfile.createIfcElementQuantity( ifcopenshell.guid.new(), @@ -1132,7 +1144,7 @@ def export(exportList,filename,colors=None,preferences=None): if anno.isDerivedFrom("Part::Feature"): reps = [] sh = anno.Shape.copy() - sh.scale(0.001) # to meters + sh.scale(preferences['SCALE_FACTOR']) # to meters ehc = [] curves = [] for w in sh.Wires: @@ -1148,7 +1160,7 @@ def export(exportList,filename,colors=None,preferences=None): if curves: reps.append(ifcfile.createIfcGeometricCurveSet(curves)) elif anno.isDerivedFrom("App::Annotation"): - l = FreeCAD.Vector(anno.Position).multiply(0.001) + l = FreeCAD.Vector(anno.Position).multiply(preferences['SCALE_FACTOR']) pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) s = ";".join(anno.LabelText) @@ -1157,7 +1169,7 @@ def export(exportList,filename,colors=None,preferences=None): txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT") reps = [txt] elif Draft.getType(anno) == "DraftText": - l = FreeCAD.Vector(anno.Placement.Base).multiply(0.001) + l = FreeCAD.Vector(anno.Placement.Base).multiply(preferences['SCALE_FACTOR']) pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) s = ";".join(anno.Text) @@ -1494,7 +1506,7 @@ def getIfcTypeFromObj(obj): return "Ifc" + ifctype -def exportIFC2X3Attributes(obj, kwargs): +def exportIFC2X3Attributes(obj, kwargs, scale=0.001): ifctype = getIfcTypeFromObj(obj) if ifctype in ["IfcSlab", "IfcFooting"]: @@ -1515,7 +1527,7 @@ def exportIFC2X3Attributes(obj, kwargs): kwargs.update({ "CompositionType": "ELEMENT", "InteriorOrExteriorSpace": internal, - "ElevationWithFlooring": obj.Shape.BoundBox.ZMin/1000.0 + "ElevationWithFlooring": obj.Shape.BoundBox.ZMin*scale }) elif ifctype == "IfcReinforcingBar": kwargs.update({ @@ -1523,11 +1535,11 @@ def exportIFC2X3Attributes(obj, kwargs): "BarLength": obj.Length.Value }) elif ifctype == "IfcBuildingStorey": - kwargs.update({"Elevation": obj.Placement.Base.z/1000.0}) + kwargs.update({"Elevation": obj.Placement.Base.z*scale}) return kwargs -def exportIfcAttributes(obj, kwargs): +def exportIfcAttributes(obj, kwargs, scale=0.001): for property in obj.PropertiesList: if obj.getGroupOfProperty(property) == "IFC Attributes" and obj.getPropertyByName(property): @@ -1535,7 +1547,7 @@ def exportIfcAttributes(obj, kwargs): if isinstance(value, FreeCAD.Units.Quantity): value = float(value) if property in ["ElevationWithFlooring","Elevation"]: - value = value/1000 # some properties must be changed to meters + value = value*scale # some properties must be changed to meters kwargs.update({property: value}) return kwargs @@ -1756,7 +1768,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess 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))) + origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(preferences['SCALE_FACTOR']))) transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3) mapitem = ifcfile.createIfcMappedItem(repmap,transf) shapes = [mapitem] @@ -1779,9 +1791,9 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess rdata = obj.Proxy.getRebarData(obj) if rdata: # convert to meters - r = rdata[1] * 0.001 + r = rdata[1] * preferences['SCALE_FACTOR'] for w in rdata[0]: - w.scale(0.001) + w.scale(preferences['SCALE_FACTOR']) cur = createCurve(ifcfile,w) shape = ifcfile.createIfcSweptDiskSolid(cur,r) shapes.append(shape) @@ -1803,17 +1815,17 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess pl = [pl] for i in range(len(p)): pi = p[i] - pi.scale(0.001) + pi.scale(preferences['SCALE_FACTOR']) if i < len(ev): evi = FreeCAD.Vector(ev[i]) else: evi = FreeCAD.Vector(ev[-1]) - evi.multiply(0.001) + evi.multiply(preferences['SCALE_FACTOR']) if i < len(pl): pli = pl[i].copy() else: pli = pl[-1].copy() - pli.Base = pli.Base.multiply(0.001) + pli.Base = pli.Base.multiply(preferences['SCALE_FACTOR']) pstr = str([v.Point for v in p[i].Vertexes]) if pstr in profiledefs: profile = profiledefs[pstr] @@ -1828,7 +1840,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess 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) + pl2.Base = pl2.Base.multiply(preferences['SCALE_FACTOR']) 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)))) @@ -1891,7 +1903,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess if obj.Shape.Faces: sh = obj.Shape.copy() sh.Placement = obj.getGlobalPlacement() - sh.scale(0.001) # to meters + sh.scale(preferences['SCALE_FACTOR']) # to meters p = geom.serialise(sh.exportBrepToString()) if p: productdef = ifcfile.add(p) @@ -1923,7 +1935,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess #if preferences['DEBUG']: print("Warning! object contains no solids") for fcsolid in dataset: - fcsolid.scale(0.001) # to meters + fcsolid.scale(preferences['SCALE_FACTOR']) # to meters faces = [] curves = False shapetype = "brep" @@ -2017,7 +2029,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess 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))) + origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(preferences['SCALE_FACTOR']))) 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) diff --git a/src/Mod/Arch/exportIFCHelper.py b/src/Mod/Arch/exportIFCHelper.py index b2b4109dd2..e559c3f727 100644 --- a/src/Mod/Arch/exportIFCHelper.py +++ b/src/Mod/Arch/exportIFCHelper.py @@ -39,6 +39,27 @@ def getObjectsOfIfcType(objects, ifcType): return results +def writeUnits(ifcfile,unit="metre"): + + """adds additional units settings to the given ifc file if needed""" + # so far, only metre or foot possible (which is all revit knows anyway) + + if unit == "foot": + d1 = ifcfile.createIfcDimensionalExponents(1,0,0,0,0,0,0); + d2 = ifcfile.createIfcMeasureWithUnit(ifcfile.createIfcRatioMeasure(0.3048),ifcfile[13]) + d3 = ifcfile.createIfcConversionBasedUnit(d1,'LENGTHUNIT','FOOT',d2) + d4 = ifcfile.createIfcDimensionalExponents(2,0,0,0,0,0,0); + d5 = ifcfile.createIfcMeasureWithUnit(ifcfile.createIfcRatioMeasure(0.09290304000000001),ifcfile[14]) + d6 = ifcfile.createIfcConversionBasedUnit(d4,'AREAUNIT','SQUARE FOOT',d5) + d7 = ifcfile.createIfcDimensionalExponents(3,0,0,0,0,0,0); + d8 = ifcfile.createIfcMeasureWithUnit(ifcfile.createIfcRatioMeasure(0.028316846592),ifcfile[15]) + d9 = ifcfile.createIfcConversionBasedUnit(d7,'VOLUMEUNIT','CUBIC FOOT',d8) + ifcfile.createIfcUnitAssignment((d3,d6,d9,ifcfile[18])) + else: # default = metre, no need to add anything + ifcfile.createIfcUnitAssignment((ifcfile[13],ifcfile[14],ifcfile[15],ifcfile[18])) + return ifcfile + + class SIUnitCreator: def __init__(self, file, text, type): self.prefixes = [