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 = [