Arch: Added option to export IFC files in imperial units

This commit is contained in:
Yorik van Havre
2019-12-23 12:33:42 -03:00
parent 242d151323
commit 26a0aa5483
3 changed files with 99 additions and 32 deletions

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>463</width>
<height>910</height>
<height>937</height>
</rect>
</property>
<property name="windowTitle">
@@ -634,6 +634,40 @@ A building storey is not mandatory but a common practice to have at least one in
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>IFC file units</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefComboBox" name="comboBox_3">
<property name="toolTip">
<string>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.</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcUnit</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
<item>
<property name="text">
<string>Metric</string>
</property>
</item>
<item>
<property name="text">
<string>Imperial</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>

View File

@@ -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)

View File

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