Arch: Further cleaning of IFC importer - moved aux functions to importIFCHelper

This commit is contained in:
Yorik van Havre
2019-08-20 19:44:19 -03:00
parent 6795a28c99
commit efd9473f23
2 changed files with 354 additions and 328 deletions

View File

@@ -239,6 +239,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
certain object ids (will also get their children) and root can be used to
import only the derivates of a certain element type (default = ifcProduct)."""
# read preference settings
getPreferences()
try:
@@ -253,18 +254,22 @@ def insert(filename,docname,skip=[],only=[],root=None):
except:
doc = FreeCAD.newDocument(docname)
FreeCAD.ActiveDocument = doc
if DEBUG: print("done.")
global ROOT_ELEMENT, parametrics
# allow to override the root element
if root:
ROOT_ELEMENT = root
# global ifcfile # keeping global for debugging purposes
# keeping global variable for debugging purposes
# global ifcfile
filename = decode(filename,utf=True)
ifcfile = ifcopenshell.open(filename)
# get file scale
ifcscale = importIFCHelper.getScaling(ifcfile)
# IfcOpenShell multiplies the precision value of the file by 100
# So we raise the precision by 100 too to compensate...
@@ -284,17 +289,15 @@ def insert(filename,docname,skip=[],only=[],root=None):
if SPLIT_LAYERS and hasattr(settings,"APPLY_LAYERSETS"):
settings.set(settings.APPLY_LAYERSETS,True)
if DEBUG: print("Building relationships table...",end="")
# build all needed tables
if DEBUG: print("Building relationships table...",end="")
objects,prodrepr,additions,groups,subtractions,colors,shapes, \
structshapes,mattable,sharedobjects,parametrics,profiles, \
sites,buildings,floors,products,openings,annotations,materials, \
style_material_id = importIFCHelper.buildRelationships(ifcfile,ROOT_ELEMENT)
if DEBUG: print("done.")
# only import a list of IDs and their children
# only import a list of IDs and their children, if defined
if only:
ids = []
while only:
@@ -304,17 +307,20 @@ def insert(filename,docname,skip=[],only=[],root=None):
only.extend(additions[currentid])
products = [ifcfile[currentid] for currentid in ids]
# start the actual import, set FreeCAD UI
count = 0
from FreeCAD import Base
progressbar = Base.ProgressIndicator()
progressbar.start("Importing IFC objects...",len(products))
if DEBUG: print("Prsing",len(products),"BIM objects...")
if DEBUG: print("Parsing",len(products),"BIM objects...")
# Prepare the 3D view if applicable
if FITVIEW_ONIMPORT and FreeCAD.GuiUp:
overallboundbox = None
import FreeCADGui
FreeCADGui.ActiveDocument.activeView().viewAxonometric()
# Create the base project object
projectImporter = importIFCHelper.ProjectImporter(ifcfile, objects)
projectImporter.execute()
@@ -329,11 +335,10 @@ def insert(filename,docname,skip=[],only=[],root=None):
ptype = product.is_a()
if DEBUG: print(count,"/",len(products),"object #"+str(pid),":",ptype,end="")
# get psets
psets = getIfcPropertySets(ifcfile, pid)
# build list of related property sets
psets = importIFCHelper.getIfcPropertySets(ifcfile, pid)
# checking for full FreeCAD parametric definition, overriding everything else
if psets and FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("IfcImportFreeCADProperties",False):
if "FreeCADPropertySet" in [ifcfile[pset].Name for pset in psets.keys()]:
if DEBUG: print(" restoring from parametric definition...",end="")
@@ -343,21 +348,22 @@ def insert(filename,docname,skip=[],only=[],root=None):
if DEBUG: print("done")
continue
else:
print("failed.",end="")
if DEBUG: print("failed")
# no parametric data, we go the good old way
name = str(ptype[3:])
if product.Name:
name = product.Name
if six.PY2:
name = name.encode("utf8")
if PREFIX_NUMBERS: name = "ID" + str(pid) + " " + name
if PREFIX_NUMBERS:
name = "ID" + str(pid) + " " + name
obj = None
baseobj = None
brep = None
shape = None
# classify object and verify if we must skip it
archobj = True # assume all objects not in structuralifcobjects are architecture
structobj = False
if ptype in structuralifcobjects:
@@ -379,8 +385,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
if DEBUG: print(" skipped.")
continue
# detect if this object is sharing its shape
# check if this object is sharing its shape (mapped representation)
clone = None
store = None
prepr = None
@@ -389,18 +394,17 @@ def insert(filename,docname,skip=[],only=[],root=None):
except:
if DEBUG: print(" ERROR unable to get object representation",end="")
if prepr and (MERGE_MODE_ARCH == 0) and archobj and CREATE_CLONES:
for s in prepr.Representations:
if s.RepresentationIdentifier.upper() == "BODY":
if s.Items[0].is_a("IfcMappedItem"):
bid = s.Items[0].MappingSource.id()
if bid in sharedobjects:
clone = sharedobjects[bid]
for r in prepr.Representations:
if r.RepresentationIdentifier.upper() == "BODY":
if r.Items[0].is_a("IfcMappedItem"):
originalid = r.Items[0].MappingSource.id()
if originalid in sharedobjects:
clone = sharedobjects[originalid]
else:
sharedobjects[bid] = None
store = bid
sharedobjects[originalid] = None
store = originalid # flag this object to be stored later
# additional setting for structural entities
# set additional setting for structural entities
if hasattr(settings,"INCLUDE_CURVES"):
if structobj:
settings.set(settings.INCLUDE_CURVES,True)
@@ -412,17 +416,23 @@ def insert(filename,docname,skip=[],only=[],root=None):
except:
pass # IfcOpenShell will yield an error if a given product has no shape, but we don't care, we're brave enough
# from now on we have a brep string
if brep:
if DEBUG: print(" "+str(int(len(brep)/1000))+"k ",end="")
# create a Part shape
shape = Part.Shape()
shape.importBrepFromString(brep,False)
shape.scale(1000.0) # IfcOpenShell always outputs in meters, we convert to mm, the freecad internal unit
if not shape.isNull():
if shape.isNull():
if DEBUG: print("null shape ",end="")
elif not shape.isValid():
if DEBUG: print("invalid shape ",end="")
else:
# add to the global boundbox if applicable
if FITVIEW_ONIMPORT and FreeCAD.GuiUp:
# add to the global boundbox
try:
bb = shape.BoundBox
# if DEBUG: print(' ' + str(bb),end="")
@@ -437,8 +447,9 @@ def insert(filename,docname,skip=[],only=[],root=None):
overallboundbox.add(bb)
if (MERGE_MODE_ARCH > 0 and archobj) or structobj:
# we are not using Arch objects
# additional tweaks when not using Arch objects
# additional tweaks to set when not using Arch objects
if ptype == "IfcSpace": # do not add spaces to compounds
if DEBUG: print("skipping space ",pid,end="")
elif structobj:
@@ -456,13 +467,15 @@ def insert(filename,docname,skip=[],only=[],root=None):
if DEBUG: print("clone ",end="")
else:
if GET_EXTRUSIONS and (MERGE_MODE_ARCH != 1):
# recompose extrusions from a shape
if ptype in ["IfcWall","IfcWallStandardCase","IfcSpace"]:
sortmethod = "z"
else:
sortmethod = "area"
ex = Arch.getExtrusionData(shape,sortmethod) # is this an extrusion?
if ex:
#print("found extrusion:",ex)
# check for extrusion profile
baseface = None
profileid = None
@@ -473,10 +486,12 @@ def insert(filename,docname,skip=[],only=[],root=None):
if product.Representation.Representations[0].Items:
if product.Representation.Representations[0].Items[0].is_a("IfcExtrudedAreaSolid"):
profileid = product.Representation.Representations[0].Items[0].SweptArea.id()
if profileid and profileid in profiles:
# reuse existing profile
if profileid and (profileid in profiles):
# reuse existing profile if existing
print("shared extrusion ",end="")
baseface = profiles[profileid]
# calculate delta placement between stored profile and this one
addplacement = FreeCAD.Placement()
r = FreeCAD.Rotation(baseface.Shape.Faces[0].normalAt(0,0),ex[0].Faces[0].normalAt(0,0))
@@ -488,7 +503,9 @@ def insert(filename,docname,skip=[],only=[],root=None):
d = ex[0].CenterOfMass.sub(baseface.Shape.CenterOfMass)
if d.Length > 0.000001:
addplacement.move(d)
if not baseface:
# this is an extrusion but we haven't built the profile yet
print("extrusion ",end="")
import DraftGeomUtils
if DraftGeomUtils.hasCurves(ex[0]) or len(ex[0].Wires) != 1:
@@ -510,6 +527,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
# no hole and no curves, we make a Draft Wire instead
baseface = Draft.makeWire([v.Point for v in ex[0].Wires[0].OrderedVertexes],closed=True)
if profileid:
# store for possible shared use
profiles[profileid] = baseface
baseobj = FreeCAD.ActiveDocument.addObject("Part::Extrusion",name+"_body")
baseobj.Base = baseface
@@ -524,15 +542,12 @@ def insert(filename,docname,skip=[],only=[],root=None):
if (not baseobj):
baseobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
baseobj.Shape = shape
else:
if DEBUG: print("null shape ",end="")
if not shape.isValid():
if DEBUG: print("invalid shape ",end="")
#continue
else:
# this object has no shape (storeys, etc...)
if DEBUG: print(" no brep ",end="")
# we now have the shape, we create the final object
if MERGE_MODE_ARCH == 0 and archobj:
# full Arch objects
@@ -544,6 +559,8 @@ def insert(filename,docname,skip=[],only=[],root=None):
if clone:
obj = getattr(Arch,"make"+freecadtype)(name=name)
obj.CloneOf = clone
# calculate the correct distance from the cloned object
if shape:
if shape.Solids:
s1 = shape.Solids[0]
@@ -556,7 +573,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
if hasattr(s1,"CenterOfMass") and hasattr(s2,"CenterOfMass"):
v = s1.CenterOfMass.sub(s2.CenterOfMass)
if product.Representation:
r = getRotation(product.Representation.Representations[0].Items[0].MappingTarget)
r = importIFCHelper.getRotation(product.Representation.Representations[0].Items[0].MappingTarget)
if not r.isNull():
v = v.add(s2.CenterOfMass)
v = v.add(r.multVec(s2.CenterOfMass.negative()))
@@ -565,9 +582,11 @@ def insert(filename,docname,skip=[],only=[],root=None):
else:
print("failed to compute placement ",)
else:
# no clone
obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name)
if freecadtype in ["Wall","Structure"] and baseobj and baseobj.isDerivedFrom("Part::Extrusion"):
# remove intermediary extrusion for types that can extrude themselves
# TODO do this check earlier so we do not calculate it, to save time
obj.Base = baseobj.Base
obj.Placement = obj.Placement.multiply(baseobj.Placement)
obj.Height = baseobj.Dir.Length
@@ -582,15 +601,18 @@ def insert(filename,docname,skip=[],only=[],root=None):
if store:
sharedobjects[store] = obj
# set the placement from the storey's elevation property
if ptype == "IfcBuildingStorey":
if product.Elevation:
obj.Placement.Base.z = product.Elevation * getScaling(ifcfile)
obj.Placement.Base.z = product.Elevation * ifcscale
break
if not obj:
# we couldn't make an object of a specific type, use default arch component
obj = Arch.makeComponent(baseobj,name=name)
# set additional properties
obj.Label = name
if DEBUG: print(": "+obj.Label+" ",end="")
if hasattr(obj,"Description") and hasattr(product,"Description"):
@@ -631,11 +653,13 @@ def insert(filename,docname,skip=[],only=[],root=None):
# This might not coincide with the file being opened, hence some attributes are not properly read.
if obj:
s = ""
if hasattr(obj,"Shape"):
if obj.Shape.Solids:
s = str(len(obj.Shape.Solids))+" solids"
if DEBUG: print(s,end="")
# print number of solids
if DEBUG:
s = ""
if hasattr(obj,"Shape"):
if obj.Shape.Solids:
s = str(len(obj.Shape.Solids))+" solids"
print(s,end="")
objects[pid] = obj
elif (MERGE_MODE_ARCH == 1 and archobj) or (MERGE_MODE_STRUCT == 0 and not archobj):
@@ -648,7 +672,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name)
if ptype == "IfcBuildingStorey":
if product.Elevation:
obj.Placement.Base.z = product.Elevation * getScaling(ifcfile)
obj.Placement.Base.z = product.Elevation * ifcscale
elif baseobj:
obj = Arch.makeComponent(baseobj,name=name,delete=True)
obj.Label = name
@@ -676,7 +700,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name)
if ptype == "IfcBuildingStorey":
if product.Elevation:
obj.Placement.Base.z = product.Elevation * getScaling(ifcfile)
obj.Placement.Base.z = product.Elevation * ifcscale
elif baseobj:
obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
obj.Shape = shape
@@ -743,7 +767,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
# 0.18 behaviour: properties are saved as pset;;type;;value in IfcProperties
d = obj.IfcProperties
obj.IfcProperties = getIfcProperties(ifcfile, pid, psets, d)
obj.IfcProperties = importIFCHelper.getIfcProperties(ifcfile, pid, psets, d)
elif hasattr(obj,"IfcData"):
@@ -778,7 +802,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
if product.is_a("IfcSite"):
if product.RefElevation:
obj.Elevation = product.RefElevation * getScaling(ifcfile)
obj.Elevation = product.RefElevation * ifcscale
if product.RefLatitude:
obj.Latitude = dms2dd(*product.RefLatitude)
if product.RefLongitude:
@@ -984,8 +1008,6 @@ def insert(filename,docname,skip=[],only=[],root=None):
prodcount = count
count = 0
scaling = getScaling(ifcfile)
#print("scaling factor =",scaling)
for annotation in annotations:
anno = None
@@ -1009,7 +1031,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
uvwaxes = uvwaxes + annotation.WAxes
for axis in uvwaxes:
if axis.AxisCurve:
sh = setRepresentation(axis.AxisCurve,scaling)
sh = importIFCHelper.get2DShape(axis.AxisCurve,ifcscale)
if sh and (len(sh[0].Vertexes) == 2): # currently only straight axes are supported
sh = sh[0]
l = sh.Length
@@ -1043,9 +1065,9 @@ def insert(filename,docname,skip=[],only=[],root=None):
shapes2d = []
for rep in annotation.Representation.Representations:
if rep.RepresentationIdentifier in ["Annotation","FootPrint","Axis"]:
sh = setRepresentation(rep,scaling)
sh = importIFCHelper.get2DShape(rep,ifcscale)
if sh in FreeCAD.ActiveDocument.Objects:
# dirty hack: setRepresentation might return an object directly if non-shape based (texts for ex)
# dirty hack: get2DShape might return an object directly if non-shape based (texts for ex)
anno = sh
else:
shapes2d.extend(sh)
@@ -1054,7 +1076,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
if DEBUG: print(" shape")
anno = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
anno.Shape = sh
p = getPlacement(annotation.ObjectPlacement,scaling)
p = importIFCHelper.getPlacement(annotation.ObjectPlacement,ifcscale)
if p: # and annotation.is_a("IfcAnnotation"):
anno.Placement = p
else:
@@ -1156,71 +1178,6 @@ def insert(filename,docname,skip=[],only=[],root=None):
return doc
# ************************************************************************************************
# ********** helper for import IFC **************
def getRelProperties(ifcfile):
# this method no longer used by this importer module
# but this relation table might be useful anyway for other purposes
properties = {} # { objid : { psetid : [propertyid, ... ], ... }, ... }
for r in ifcfile.by_type("IfcRelDefinesByProperties"):
for obj in r.RelatedObjects:
if not obj.id() in properties:
properties[obj.id()] = {}
psets = {}
props = []
if r.RelatingPropertyDefinition.is_a("IfcPropertySet"):
props.extend([prop.id() for prop in r.RelatingPropertyDefinition.HasProperties])
psets[r.RelatingPropertyDefinition.id()] = props
properties[obj.id()].update(psets)
return properties
def getIfcPropertySets(ifcfile, pid):
# get psets for this pid
psets = {}
for rel in ifcfile[pid].IsDefinedBy:
# the following if condition is needed in IFC2x3 only
# https://forum.freecadweb.org/viewtopic.php?f=39&t=37892#p322884
if rel.is_a('IfcRelDefinesByProperties'):
props = []
if rel.RelatingPropertyDefinition.is_a("IfcPropertySet"):
props.extend([prop.id() for prop in rel.RelatingPropertyDefinition.HasProperties])
psets[rel.RelatingPropertyDefinition.id()] = props
return psets
def getIfcProperties(ifcfile, pid, psets, d):
for pset in psets.keys():
#print("reading pset: ",pset)
psetname = ifcfile[pset].Name
if six.PY2:
psetname = psetname.encode("utf8")
for prop in psets[pset]:
e = ifcfile[prop]
pname = e.Name
if six.PY2:
pname = pname.encode("utf8")
if e.is_a("IfcPropertySingleValue"):
if e.NominalValue:
ptype = e.NominalValue.is_a()
if ptype in ['IfcLabel','IfcText','IfcIdentifier','IfcDescriptiveMeasure']:
pvalue = e.NominalValue.wrappedValue
if six.PY2:
pvalue = pvalue.encode("utf8")
else:
pvalue = str(e.NominalValue.wrappedValue)
if hasattr(e.NominalValue,'Unit'):
if e.NominalValue.Unit:
pvalue += e.NominalValue.Unit
d[pname+";;"+psetname] = ptype+";;"+pvalue
#print("adding property: ",pname,ptype,pvalue," pset ",psetname)
return d
def createFromProperties(propsets,ifcfile):
@@ -1307,210 +1264,3 @@ def createFromProperties(propsets,ifcfile):
else:
print("Unhandled FreeCAD property:",name," of type:",ptype)
return obj
# Below are 2D helper functions needed while IfcOpenShell cannot do this itself...
def setRepresentation(representation,scaling=1000):
"""Returns a shape from a 2D IfcShapeRepresentation"""
def getPolyline(ent):
pts = []
for p in ent.Points:
c = p.Coordinates
c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0)
c.multiply(scaling)
pts.append(c)
return Part.makePolygon(pts)
def getLine(ent):
pts = []
p1 = getVector(ent.Pnt)
p1.multiply(scaling)
pts.append(p1)
p2 = getVector(ent.Dir)
p2.multiply(scaling)
p2 = p1.add(p2)
pts.append(p2)
return Part.makePolygon(pts)
def getCircle(ent):
c = ent.Position.Location.Coordinates
c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0)
c.multiply(scaling)
r = ent.Radius*scaling
return Part.makeCircle(r,c)
def getCurveSet(ent):
result = []
if ent.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]:
elts = ent.Elements
elif ent.is_a() in ["IfcLine","IfcPolyline","IfcCircle","IfcTrimmedCurve"]:
elts = [ent]
for el in elts:
if el.is_a("IfcPolyline"):
result.append(getPolyline(el))
elif el.is_a("IfcLine"):
result.append(getLine(el))
elif el.is_a("IfcCircle"):
result.append(getCircle(el))
elif el.is_a("IfcTrimmedCurve"):
base = el.BasisCurve
t1 = el.Trim1[0].wrappedValue
t2 = el.Trim2[0].wrappedValue
if not el.SenseAgreement:
t1,t2 = t2,t1
if base.is_a("IfcPolyline"):
bc = getPolyline(base)
result.append(bc)
elif base.is_a("IfcCircle"):
bc = getCircle(base)
e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape()
d = base.Position.RefDirection.DirectionRatios
v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0)
a = -DraftVecUtils.angle(v)
e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a))
result.append(e)
elif el.is_a("IfcCompositeCurve"):
for base in el.Segments:
if base.ParentCurve.is_a("IfcPolyline"):
bc = getPolyline(base.ParentCurve)
result.append(bc)
elif base.ParentCurve.is_a("IfcCircle"):
bc = getCircle(base.ParentCurve)
e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape()
d = base.Position.RefDirection.DirectionRatios
v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0)
a = -DraftVecUtils.angle(v)
e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a))
result.append(e)
return result
result = []
if representation.is_a("IfcShapeRepresentation"):
for item in representation.Items:
if item.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]:
result = getCurveSet(item)
elif item.is_a("IfcMappedItem"):
preresult = setRepresentation(item.MappingSource.MappedRepresentation,scaling)
pla = getPlacement(item.MappingSource.MappingOrigin,scaling)
rot = getRotation(item.MappingTarget)
if pla:
if rot.Angle:
pla.Rotation = rot
for r in preresult:
#r.Placement = pla
result.append(r)
else:
result = preresult
elif item.is_a("IfcTextLiteral"):
t = Draft.makeText([item.Literal],point=getPlacement(item.Placement,scaling).Base)
return t # dirty hack... Object creation should not be done here
elif representation.is_a() in ["IfcPolyline","IfcCircle","IfcTrimmedCurve"]:
result = getCurveSet(representation)
return result
def getRotation(entity):
"returns a FreeCAD rotation from an IfcProduct with a IfcMappedItem representation"
try:
u = FreeCAD.Vector(entity.Axis1.DirectionRatios)
v = FreeCAD.Vector(entity.Axis2.DirectionRatios)
w = FreeCAD.Vector(entity.Axis3.DirectionRatios)
except AttributeError:
return FreeCAD.Rotation()
import WorkingPlane
p = WorkingPlane.plane(u=u,v=v,w=w)
return p.getRotation().Rotation
def getPlacement(entity,scaling=1000):
"returns a placement from the given entity"
if not entity:
return None
import DraftVecUtils
pl = None
if entity.is_a("IfcAxis2Placement3D"):
x = getVector(entity.RefDirection,scaling)
z = getVector(entity.Axis,scaling)
if x and z:
y = z.cross(x)
m = DraftVecUtils.getPlaneRotation(x,y,z)
pl = FreeCAD.Placement(m)
else:
pl = FreeCAD.Placement()
loc = getVector(entity.Location,scaling)
if loc:
pl.move(loc)
elif entity.is_a("IfcLocalPlacement"):
pl = getPlacement(entity.PlacementRelTo,1) # original placement
relpl = getPlacement(entity.RelativePlacement,1) # relative transf
if pl and relpl:
pl = pl.multiply(relpl)
elif relpl:
pl = relpl
elif entity.is_a("IfcCartesianPoint"):
loc = getVector(entity,scaling)
pl = FreeCAD.Placement()
pl.move(loc)
if pl:
pl.Base = FreeCAD.Vector(pl.Base).multiply(scaling)
return pl
def getVector(entity,scaling=1000):
"returns a vector from the given entity"
if not entity:
return None
v = None
if entity.is_a("IfcDirection"):
if len(entity.DirectionRatios) == 3:
v= FreeCAD.Vector(tuple(entity.DirectionRatios))
else:
v = FreeCAD.Vector(tuple(entity.DirectionRatios+[0]))
elif entity.is_a("IfcCartesianPoint"):
if len(entity.Coordinates) == 3:
v = FreeCAD.Vector(tuple(entity.Coordinates))
else:
v = FreeCAD.Vector(tuple(entity.Coordinates+[0]))
#if v:
# v.multiply(scaling)
return v
def getScaling(ifcfile):
"returns a scaling factor from file units to mm"
def getUnit(unit):
if unit.Name == "METRE":
if unit.Prefix == "KILO":
return 1000000.0
elif unit.Prefix == "HECTO":
return 100000.0
elif unit.Prefix == "DECA":
return 10000.0
elif not unit.Prefix:
return 1000.0
elif unit.Prefix == "DECI":
return 100.0
elif unit.Prefix == "CENTI":
return 10.0
return 1.0
ua = ifcfile.by_type("IfcUnitAssignment")
if not ua:
return 1.0
ua = ua[0]
for u in ua.Units:
if u.UnitType == "LENGTHUNIT":
if u.is_a("IfcConversionBasedUnit"):
f = getUnit(u.ConversionFactor.UnitComponent)
return f * u.ConversionFactor.ValueComponent.wrappedValue
elif u.is_a("IfcSIUnit") or u.is_a("IfcUnit"):
return getUnit(u)
return 1.0

View File

@@ -1,6 +1,13 @@
import Arch, ArchIFC, math
import FreeCAD
import Arch
import ArchIFC
import math
import six
class ProjectImporter:
"""A helper class to create a FreeCAD Arch Project object"""
def __init__(self, file, objects):
self.file = file
self.objects = objects
@@ -176,3 +183,272 @@ def buildRelationships(ifcfile,root_element):
style_material_id
def getRelProperties(ifcfile):
"""Builds and returns a dictionary of {object:[properties]} from an IFC file"""
# this method no longer used by this importer module
# but this relation table might be useful anyway for other purposes
properties = {} # { objid : { psetid : [propertyid, ... ], ... }, ... }
for r in ifcfile.by_type("IfcRelDefinesByProperties"):
for obj in r.RelatedObjects:
if not obj.id() in properties:
properties[obj.id()] = {}
psets = {}
props = []
if r.RelatingPropertyDefinition.is_a("IfcPropertySet"):
props.extend([prop.id() for prop in r.RelatingPropertyDefinition.HasProperties])
psets[r.RelatingPropertyDefinition.id()] = props
properties[obj.id()].update(psets)
return properties
def getIfcPropertySets(ifcfile, pid):
"""Returns a dicionary of {pset_id:[prop_id, prop_id...]} for an IFC object"""
# get psets for this pid
psets = {}
for rel in ifcfile[pid].IsDefinedBy:
# the following if condition is needed in IFC2x3 only
# https://forum.freecadweb.org/viewtopic.php?f=39&t=37892#p322884
if rel.is_a('IfcRelDefinesByProperties'):
props = []
if rel.RelatingPropertyDefinition.is_a("IfcPropertySet"):
props.extend([prop.id() for prop in rel.RelatingPropertyDefinition.HasProperties])
psets[rel.RelatingPropertyDefinition.id()] = props
return psets
def getScaling(ifcfile):
"""returns a scaling factor from file units to mm"""
def getUnit(unit):
if unit.Name == "METRE":
if unit.Prefix == "KILO":
return 1000000.0
elif unit.Prefix == "HECTO":
return 100000.0
elif unit.Prefix == "DECA":
return 10000.0
elif not unit.Prefix:
return 1000.0
elif unit.Prefix == "DECI":
return 100.0
elif unit.Prefix == "CENTI":
return 10.0
return 1.0
ua = ifcfile.by_type("IfcUnitAssignment")
if not ua:
return 1.0
ua = ua[0]
for u in ua.Units:
if u.UnitType == "LENGTHUNIT":
if u.is_a("IfcConversionBasedUnit"):
f = getUnit(u.ConversionFactor.UnitComponent)
return f * u.ConversionFactor.ValueComponent.wrappedValue
elif u.is_a("IfcSIUnit") or u.is_a("IfcUnit"):
return getUnit(u)
return 1.0
def getRotation(entity):
"""returns a FreeCAD rotation from an IfcProduct with a IfcMappedItem representation"""
try:
u = FreeCAD.Vector(entity.Axis1.DirectionRatios)
v = FreeCAD.Vector(entity.Axis2.DirectionRatios)
w = FreeCAD.Vector(entity.Axis3.DirectionRatios)
except AttributeError:
return FreeCAD.Rotation()
import WorkingPlane
p = WorkingPlane.plane(u=u,v=v,w=w)
return p.getRotation().Rotation
def getPlacement(entity,scaling=1000):
"""returns a placement from the given entity"""
if not entity:
return None
import DraftVecUtils
pl = None
if entity.is_a("IfcAxis2Placement3D"):
x = getVector(entity.RefDirection,scaling)
z = getVector(entity.Axis,scaling)
if x and z:
y = z.cross(x)
m = DraftVecUtils.getPlaneRotation(x,y,z)
pl = FreeCAD.Placement(m)
else:
pl = FreeCAD.Placement()
loc = getVector(entity.Location,scaling)
if loc:
pl.move(loc)
elif entity.is_a("IfcLocalPlacement"):
pl = getPlacement(entity.PlacementRelTo,1) # original placement
relpl = getPlacement(entity.RelativePlacement,1) # relative transf
if pl and relpl:
pl = pl.multiply(relpl)
elif relpl:
pl = relpl
elif entity.is_a("IfcCartesianPoint"):
loc = getVector(entity,scaling)
pl = FreeCAD.Placement()
pl.move(loc)
if pl:
pl.Base = FreeCAD.Vector(pl.Base).multiply(scaling)
return pl
def getVector(entity,scaling=1000):
"""returns a vector from the given entity"""
if not entity:
return None
v = None
if entity.is_a("IfcDirection"):
if len(entity.DirectionRatios) == 3:
v= FreeCAD.Vector(tuple(entity.DirectionRatios))
else:
v = FreeCAD.Vector(tuple(entity.DirectionRatios+[0]))
elif entity.is_a("IfcCartesianPoint"):
if len(entity.Coordinates) == 3:
v = FreeCAD.Vector(tuple(entity.Coordinates))
else:
v = FreeCAD.Vector(tuple(entity.Coordinates+[0]))
#if v:
# v.multiply(scaling)
return v
def get2DShape(representation,scaling=1000):
"""Returns a shape from a 2D IfcShapeRepresentation"""
import Part,DraftVecUtils
def getPolyline(ent):
pts = []
for p in ent.Points:
c = p.Coordinates
c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0)
c.multiply(scaling)
pts.append(c)
return Part.makePolygon(pts)
def getLine(ent):
pts = []
p1 = getVector(ent.Pnt)
p1.multiply(scaling)
pts.append(p1)
p2 = getVector(ent.Dir)
p2.multiply(scaling)
p2 = p1.add(p2)
pts.append(p2)
return Part.makePolygon(pts)
def getCircle(ent):
c = ent.Position.Location.Coordinates
c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0)
c.multiply(scaling)
r = ent.Radius*scaling
return Part.makeCircle(r,c)
def getCurveSet(ent):
result = []
if ent.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]:
elts = ent.Elements
elif ent.is_a() in ["IfcLine","IfcPolyline","IfcCircle","IfcTrimmedCurve"]:
elts = [ent]
for el in elts:
if el.is_a("IfcPolyline"):
result.append(getPolyline(el))
elif el.is_a("IfcLine"):
result.append(getLine(el))
elif el.is_a("IfcCircle"):
result.append(getCircle(el))
elif el.is_a("IfcTrimmedCurve"):
base = el.BasisCurve
t1 = el.Trim1[0].wrappedValue
t2 = el.Trim2[0].wrappedValue
if not el.SenseAgreement:
t1,t2 = t2,t1
if base.is_a("IfcPolyline"):
bc = getPolyline(base)
result.append(bc)
elif base.is_a("IfcCircle"):
bc = getCircle(base)
e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape()
d = base.Position.RefDirection.DirectionRatios
v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0)
a = -DraftVecUtils.angle(v)
e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a))
result.append(e)
elif el.is_a("IfcCompositeCurve"):
for base in el.Segments:
if base.ParentCurve.is_a("IfcPolyline"):
bc = getPolyline(base.ParentCurve)
result.append(bc)
elif base.ParentCurve.is_a("IfcCircle"):
bc = getCircle(base.ParentCurve)
e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape()
d = base.Position.RefDirection.DirectionRatios
v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0)
a = -DraftVecUtils.angle(v)
e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a))
result.append(e)
return result
result = []
if representation.is_a("IfcShapeRepresentation"):
for item in representation.Items:
if item.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]:
result = getCurveSet(item)
elif item.is_a("IfcMappedItem"):
preresult = setRepresentation(item.MappingSource.MappedRepresentation,scaling)
pla = getPlacement(item.MappingSource.MappingOrigin,scaling)
rot = getRotation(item.MappingTarget)
if pla:
if rot.Angle:
pla.Rotation = rot
for r in preresult:
#r.Placement = pla
result.append(r)
else:
result = preresult
elif item.is_a("IfcTextLiteral"):
t = Draft.makeText([item.Literal],point=getPlacement(item.Placement,scaling).Base)
return t # dirty hack... Object creation should not be done here
elif representation.is_a() in ["IfcPolyline","IfcCircle","IfcTrimmedCurve"]:
result = getCurveSet(representation)
return result
def getIfcProperties(ifcfile, pid, psets, d):
"""builds valid property values for FreeCAD"""
for pset in psets.keys():
#print("reading pset: ",pset)
psetname = ifcfile[pset].Name
if six.PY2:
psetname = psetname.encode("utf8")
for prop in psets[pset]:
e = ifcfile[prop]
pname = e.Name
if six.PY2:
pname = pname.encode("utf8")
if e.is_a("IfcPropertySingleValue"):
if e.NominalValue:
ptype = e.NominalValue.is_a()
if ptype in ['IfcLabel','IfcText','IfcIdentifier','IfcDescriptiveMeasure']:
pvalue = e.NominalValue.wrappedValue
if six.PY2:
pvalue = pvalue.encode("utf8")
else:
pvalue = str(e.NominalValue.wrappedValue)
if hasattr(e.NominalValue,'Unit'):
if e.NominalValue.Unit:
pvalue += e.NominalValue.Unit
d[pname+";;"+psetname] = ptype+";;"+pvalue
#print("adding property: ",pname,ptype,pvalue," pset ",psetname)
return d