Files
create/src/Mod/Arch/exportIFCHelper.py
Roy-043 544bd1e7ea Arch: implement new get_param functions
Additionally 2 Arch_Window bugs were fixed:
* If the W1 value was changed the box tracker was not repositioned relative to the cursor.
* The WindowColor was not applied because of a typo in the code. De current default color is quite dark BTW.

Note that all dimensional values that were not really defaults, but just the last entered values, have been removed from preferences-archdefaults.ui. As a result the layout looks a bit strange. That will be improved in a next PR.
2024-01-18 15:43:15 +01:00

430 lines
17 KiB
Python

# ***************************************************************************
# * Copyright (c) 2019 Yorik van Havre <yorik@uncreated.net> *
# * *
# * 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 *
# * *
# ***************************************************************************
import json
import math
import FreeCAD
# import Draft
import ifcopenshell
from draftutils import params
def getObjectsOfIfcType(objects, ifcType):
results = []
for object in objects:
if hasattr(object,"IfcType"):
if object.IfcType == ifcType:
results.append(object)
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 = [
"EXA", "PETA", "TERA", "GIGA", "MEGA", "KILO", "HECTO",
"DECA", "DECI", "CENTI", "MILLI", "MICRO", "NANO", "PICO", "FEMTO",
"ATTO"
]
self.unitNames = [
"AMPERE", "BECQUEREL", "CANDELA", "COULOMB",
"CUBIC_METRE", "DEGREE CELSIUS", "FARAD", "GRAM", "GRAY", "HENRY",
"HERTZ", "JOULE", "KELVIN", "LUMEN", "LUX", "MOLE", "NEWTON", "OHM",
"PASCAL", "RADIAN", "SECOND", "SIEMENS", "SIEVERT", "SQUARE METRE",
"METRE", "STERADIAN", "TESLA", "VOLT", "WATT", "WEBER"
]
self.text = text
self.SIUnit = file.createIfcSIUnit(None, type, self.getSIPrefix(), self.getSIUnitName())
def getSIPrefix(self):
for prefix in self.prefixes:
if prefix in self.text.upper():
return prefix
return None
def getSIUnitName(self):
for unitName in self.unitNames:
if unitName in self.text.upper():
return unitName
return None
class ContextCreator:
def __init__(self, file, objects):
self.file = file
self.objects = objects
self.project_object = self.getProjectObject()
self.project_data = self.getProjectObjectData()
self.model_context = self.createGeometricRepresentationContext()
self.model_view_subcontext = self.createGeometricRepresentationSubContext()
self.target_crs = self.createTargetCRS()
self.map_conversion = self.createMapConversion()
self.project = self.createProject()
def createGeometricRepresentationContext(self):
return self.file.createIfcGeometricRepresentationContext(
None, "Model",
3, 1.0E-05,
self.file.by_type("IfcAxis2Placement3D")[0],
self.createTrueNorth())
def createGeometricRepresentationSubContext(self):
return self.file.createIfcGeometricRepresentationSubContext(
"Body", "Model",
None, None, None, None,
self.model_context, None, "MODEL_VIEW", None)
def createTargetCRS(self):
try:
SIUnit = SIUnitCreator(self.file, self.project_data["map_unit"], "LENGTHUNIT")
return self.file.createIfcProjectedCRS(
self.project_data["name"],
self.project_data["description"],
self.project_data["geodetic_datum"],
self.project_data["vertical_datum"],
self.project_data["map_projection"],
self.project_data["map_zone"],
SIUnit.SIUnit
)
except Exception:
return None
def createMapConversion(self):
try:
return self.file.createIfcMapConversion(
self.model_context, self.target_crs,
float(self.project_data["eastings"]),
float(self.project_data["northings"]),
float(self.project_data["orthogonal_height"]),
self.calculateXAxisAbscissa(),
self.calculateXAxisOrdinate(),
float(self.project_data["scale"])
)
except Exception:
return None
def createTrueNorth(self):
return self.file.createIfcDirection(
(self.calculateXAxisAbscissa(), self.calculateXAxisOrdinate()))
def calculateXAxisAbscissa(self):
if "true_north" in self.project_data:
return math.cos(math.radians(float(self.project_data["true_north"]) + 90))
return 0.
def calculateXAxisOrdinate(self):
if "true_north" in self.project_data:
return math.sin(math.radians(float(self.project_data["true_north"]) + 90))
return 1.
def createProject(self):
if not self.project_object:
return self.createAutomaticProject()
return self.createCustomProject()
def createAutomaticProject(self):
return self.file.createIfcProject(
self.getProjectGUID(),
self.file.by_type("IfcOwnerHistory")[0],
FreeCAD.ActiveDocument.Name, None,
None, None, None, [self.model_context],
self.file.by_type("IfcUnitAssignment")[0])
def createCustomProject(self):
return self.file.createIfcProject(
self.getProjectGUID(),
self.file.by_type("IfcOwnerHistory")[0],
self.project_object.Label, self.project_object.Description,
self.project_object.ObjectType, self.project_object.LongName,
self.project_object.Phase,
[self.model_context],
self.file.by_type("IfcUnitAssignment")[0])
def getProjectGUID(self):
# TODO: Do not generate a new one each time, but at least this one
# conforms to the community consensus on how a GUID is generated.
return ifcopenshell.guid.new()
def getProjectObject(self):
try:
return getObjectsOfIfcType(self.objects, "Project")[0]
except Exception:
return None
def getProjectObjectData(self):
if not self.project_object:
return {}
return json.loads(self.project_object.IfcData['complex_attributes'])["RepresentationContexts"]
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
def __init__(self,ifcfile,template=True):
self.ifcfile = ifcfile
self.compress = params.get_param_arch("ifcCompress")
self.mergeProfiles = params.get_param_arch("ifcMergeProfiles")
self.cartesianpoints = {}
self.directions = {}
self.axis2placement3ds = {}
if template: # we are using the default template from exportIFC.py
self.cartesianpoints = {(0,0,0):self.ifcfile[8]} # from template
self.directions = {(1,0,0):self.ifcfile[6],(0,0,1):self.ifcfile[7],(0,1,0):self.ifcfile[10]} # from template
self.axis2placement3ds = {'(0.0, 0.0, 0.0)(0.0, 0.0, 1.0)(1.0, 0.0, 0.0)':self.ifcfile[9]} # from template
self.polylines = {}
self.polyloops = {}
self.propertysinglevalues = {}
self.axis2placement2ds = {}
self.localplacements = {}
self.rgbs = {}
self.ssrenderings = {}
self.sstyles = {}
self.transformationoperators = {}
self.psas = {}
self.spared = 0
self.profiledefs = {}
def createIfcCartesianPoint(self,points):
if self.compress and points in self.cartesianpoints:
self.spared += 1
return self.cartesianpoints[points]
else:
c = self.ifcfile.createIfcCartesianPoint(points)
if self.compress:
self.cartesianpoints[points] = c
return c
def createIfcDirection(self,points):
if self.compress and points in self.directions:
self.spared += 1
return self.directions[points]
else:
c = self.ifcfile.createIfcDirection(points)
if self.compress:
self.directions[points] = c
return c
def createIfcPolyline(self,points):
key = "".join([str(p.Coordinates) for p in points])
if self.compress and key in self.polylines:
self.spared += 1
return self.polylines[key]
else:
c = self.ifcfile.createIfcPolyline(points)
if self.compress:
self.polylines[key] = c
return c
def createIfcPolyLoop(self,points):
key = "".join([str(p.Coordinates) for p in points])
if self.compress and key in self.polyloops:
self.spared += 1
return self.polyloops[key]
else:
c = self.ifcfile.createIfcPolyLoop(points)
if self.compress:
self.polyloops[key] = c
return c
def createIfcPropertySingleValue(self,name,ptype,pvalue):
key = str(name) + str(ptype) + str(pvalue)
if self.compress and key in self.propertysinglevalues:
self.spared += 1
return self.propertysinglevalues[key]
else:
if isinstance(pvalue,float) and pvalue < 0.000000001: # remove the exp notation that some bim apps hate
pvalue = 0
c = self.ifcfile.createIfcPropertySingleValue(name,None,self.ifcfile.create_entity(ptype,pvalue),None)
if self.compress:
self.propertysinglevalues[key] = c
return c
def createIfcAxis2Placement3D(self,p1=None,p2=None,p3=None):
if not p1:
p1 = self.createIfcCartesianPoint((0.0,0.0,0.0))
p2 = self.createIfcDirection((0.0,0.0,1.0))
p3 = self.createIfcDirection((1.0,0.0,0.0))
if p2:
tp2 = str(p2.DirectionRatios)
else:
tp2 = "None"
if p3:
tp3 = str(p3.DirectionRatios)
else:
tp3 = "None"
key = str(p1.Coordinates) + tp2 + tp3
if self.compress and key in self.axis2placement3ds:
self.spared += 1
return self.axis2placement3ds[key]
else:
c = self.ifcfile.createIfcAxis2Placement3D(p1,p2,p3)
if self.compress:
self.axis2placement3ds[key] = c
return c
def createIfcAxis2Placement2D(self,p1,p2):
key = str(p1.Coordinates) + str(p2.DirectionRatios)
if self.compress and key in self.axis2placement2ds:
self.spared += 1
return self.axis2placement2ds[key]
else:
c = self.ifcfile.createIfcAxis2Placement2D(p1,p2)
if self.compress:
self.axis2placement2ds[key] = c
return c
def createIfcLocalPlacement(self,gpl=None):
if not gpl:
gpl = self.createIfcAxis2Placement3D()
key = str(gpl.Location.Coordinates) + str(gpl.Axis.DirectionRatios) + str(gpl.RefDirection.DirectionRatios)
if self.compress and key in self.localplacements:
self.spared += 1
return self.localplacements[key]
else:
c = self.ifcfile.createIfcLocalPlacement(None,gpl)
if self.compress:
self.localplacements[key] = c
return c
def createIfcColourRgb(self,r,g,b):
key = (r,g,b)
if self.compress and key in self.rgbs:
self.spared += 1
return self.rgbs[key]
else:
c = self.ifcfile.createIfcColourRgb(None,r,g,b)
if self.compress:
self.rgbs[key] = c
return c
def createIfcSurfaceStyleRendering(self,col,trans=0):
key = (col.Red,col.Green,col.Blue,trans)
if self.compress and key in self.ssrenderings:
self.spared += 1
return self.ssrenderings[key]
else:
if trans == 0:
trans = None
c = self.ifcfile.createIfcSurfaceStyleRendering(col,trans,None,None,None,None,None,None,"FLAT")
if self.compress:
self.ssrenderings[key] = c
return c
def createIfcCartesianTransformationOperator3D(self,axis1,axis2,origin,scale,axis3):
key = str(axis1.DirectionRatios) + str(axis2.DirectionRatios) + str(origin.Coordinates) + str(scale) + str(axis3.DirectionRatios)
if self.compress and key in self.transformationoperators:
self.spared += 1
return self.transformationoperators[key]
else:
c = self.ifcfile.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,scale,axis3)
if self.compress:
self.transformationoperators[key] = c
return c
def createIfcSurfaceStyle(self,name,r,g,b,t=0):
if name:
key = name + str((r,g,b))
else:
key = str((r,g,b))
if self.compress and key in self.sstyles:
self.spared += 1
return self.sstyles[key]
else:
col = self.createIfcColourRgb(r,g,b)
ssr = self.createIfcSurfaceStyleRendering(col,t)
c = self.ifcfile.createIfcSurfaceStyle(name,"BOTH",[ssr])
if self.compress:
self.sstyles[key] = c
return c
def createIfcPresentationStyleAssignment(self,name,r,g,b,t=0,ifc4=False):
if name:
key = name+str((r,g,b,t))
else:
key = str((r,g,b,t))
if self.compress and key in self.psas:
self.spared += 1
return self.psas[key]
else:
iss = self.createIfcSurfaceStyle(name,r,g,b,t)
if ifc4:
c = iss
else:
c = self.ifcfile.createIfcPresentationStyleAssignment([iss])
if self.compress:
self.psas[key] = c
return c
def createIfcRectangleProfileDef(self,name,mode,pt,b,h):
key = "RECT"+str(name)+str(mode)+str(pt)+str(b)+str(h)
if self.compress and self.mergeProfiles and key in self.profiledefs:
return self.profiledefs[key]
else:
c = self.ifcfile.createIfcRectangleProfileDef(name,mode,pt,b,h)
if self.compress and self.mergeProfiles:
self.profiledefs[key] = c
return c
def createIfcCircleProfileDef(self,name,mode,pt,r):
key = "CIRC"+str(name)+str(mode)+str(pt)+str(r)
if self.compress and self.mergeProfiles and key in self.profiledefs:
return self.profiledefs[key]
else:
c = self.ifcfile.createIfcCircleProfileDef(name,mode,pt,r)
if self.compress and self.mergeProfiles:
self.profiledefs[key] = c
return c
def createIfcEllipseProfileDef(self,name,mode,pt,majr,minr):
key = "ELLI"+str(name)+str(mode)+str(pt)+str(majr)+str(minr)
if self.compress and self.mergeProfiles and key in self.profiledefs:
return self.profiledefs[key]
else:
c = self.ifcfile.createIfcEllipseProfileDef(name,mode,pt,majr,minr)
if self.compress and self.mergeProfiles:
self.profiledefs[key] = c
return c