00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 import os, re, copy
00025
00026 __title__="FreeCAD IFC parser"
00027 __author__ = "Yorik van Havre, Marijn van Aerle"
00028 __url__ = "http://free-cad.sourceforge.net"
00029
00030 '''
00031 FreeCAD IFC parser, by Yorik van Havre, based on work by Marijn van Aerle
00032
00033 Usage:
00034 import ifcReader
00035 ifcdoc = ifcReader.IfcDocument("path/to/file.ifc")
00036 print ifcdoc.Entities
00037 myent = ifcdoc.Entities[20] # returns one entity
00038 myent = ifcdoc.getEnt(20) # alternative way
00039 polylines = ifcdoc.getEnt("IFCPOLYLINE") # returns a list
00040 print myent.attributes
00041
00042 The ifc document contains a list of entities, that can be retrieved
00043 by iterating the list (indices corresponds to the entities ids)
00044 or by using the getEnt() method. All entities have id, type
00045 and attributes. Attributes can have values such as text or number,
00046 or a link to another entity.
00047
00048 Important note:
00049
00050 1) For this reader to function, you need an IFC Schema Express file (.exp)
00051 available here:
00052 http://www.steptools.com/support/stdev_docs/express/ifc2x3/ifc2x3_tc1.exp
00053 For licensing reasons we are not allowed to ship that file with FreeCAD.
00054 Just place the .exp file together with this script.
00055
00056 2) IFC files can have ordered content (ordered list, no entity number missing)
00057 or be much messier (entity numbers missing, etc). The performance of the reader
00058 will be drastically different.
00059 '''
00060
00061 IFCLINE_RE = re.compile("#(\d+)[ ]?=[ ]?(.*?)\((.*)\);[\\r]?$")
00062 DEBUG = False
00063
00064 class IfcSchema:
00065 SIMPLETYPES = ["INTEGER", "REAL", "STRING", "NUMBER", "LOGICAL", "BOOLEAN"]
00066 NO_ATTR = ["WHERE", "INVERSE","WR2","WR3", "WR4", "WR5", "UNIQUE", "DERIVE"]
00067
00068 def __init__(self, filename):
00069 self.filename = filename
00070 if not os.path.exists(filename):
00071 raise ImportError("no IFCSchema file found!")
00072 else:
00073 self.file = open(self.filename)
00074 self.data = self.file.read()
00075 self.types = self.readTypes()
00076 self.entities = self.readEntities()
00077 if DEBUG: print "Parsed from schema %s: %s entities and %s types" % (self.filename, len(self.entities), len(self.types))
00078
00079 def readTypes(self):
00080 """
00081 Parse all the possible types from the schema,
00082 returns a dictionary Name -> Type
00083 """
00084 types = {}
00085 for m in re.finditer("TYPE (.*) = (.*);", self.data):
00086 typename, typetype = m.groups()
00087 if typetype in self.SIMPLETYPES:
00088 types[typename] = typetype
00089 else:
00090 types[typename] = "#" + typetype
00091
00092 return types
00093
00094 def readEntities(self):
00095 """
00096 Parse all the possible entities from the schema,
00097 returns a dictionary of the form:
00098 { name: {
00099 "supertype": supertype,
00100 "attributes": [{ key: value }, ..]
00101 }}
00102 """
00103 entities = {}
00104
00105
00106
00107 for m in re.finditer("ENTITY (.*?)END_ENTITY;", self.data, re.DOTALL):
00108 entity = {}
00109 raw_entity_str = m.groups()[0]
00110
00111 entity["name"] = re.search("(.*?)[;|\s]", raw_entity_str).groups()[0].upper()
00112
00113 subtypeofmatch = re.search(".*SUBTYPE OF \((.*?)\);", raw_entity_str)
00114 entity["supertype"] = subtypeofmatch.groups()[0].upper() if subtypeofmatch else None
00115
00116
00117
00118 inner_str = re.search(";(.*?)$", raw_entity_str, re.DOTALL).groups()[0]
00119
00120 attrs_str = min([inner_str.partition("\r\n "+a)[0] for a in self.NO_ATTR])
00121 attrs = []
00122 for am in re.finditer("(.*?) : (.*?);", attrs_str, re.DOTALL):
00123 name, attr_type = [s.replace("\r\n\t","") for s in am.groups()]
00124 attrs.append((name, attr_type))
00125
00126 entity["attributes"] = attrs
00127 entities[entity["name"]] = entity
00128
00129
00130 return entities
00131
00132 def getAttributes(self, name):
00133 """
00134 Get all attributes af an entity, including supertypes
00135 """
00136 ent = self.entities[name]
00137
00138 attrs = []
00139 while ent != None:
00140 this_ent_attrs = copy.copy(ent["attributes"])
00141 this_ent_attrs.reverse()
00142 attrs.extend(this_ent_attrs)
00143 ent = self.entities.get(ent["supertype"], None)
00144
00145 attrs.reverse()
00146 return attrs
00147
00148 class IfcFile:
00149 """
00150 Parses an ifc file given by filename, entities can be retrieved by name and id
00151 The whole file is stored in a dictionary (in memory)
00152 """
00153
00154 entsById = {}
00155 entsByName = {}
00156
00157 def __init__(self, filename,schema):
00158 self.filename = filename
00159 self.schema = IfcSchema(schema)
00160 self.file = open(self.filename)
00161 self.entById, self.entsByName, self.header = self.read()
00162 self.file.close()
00163 if DEBUG: print "Parsed from file %s: %s entities" % (self.filename, len(self.entById))
00164
00165 def getEntityById(self, id):
00166 return self.entById.get(id, None)
00167
00168 def getEntitiesByName(self, name):
00169 return self.entsByName.get(name, None)
00170
00171 def read(self):
00172 """
00173 Returns 2 dictionaries, entById and entsByName
00174 """
00175 entById = {}
00176 entsByName = {}
00177 header = 'HEADER '
00178 readheader = False
00179 for line in self.file:
00180 e = self.parseLine(line)
00181 if e:
00182 entById[int(e["id"])] = e
00183 ids = e.get(e["name"],[])
00184 ids.append(e["id"])
00185 entsByName[e["name"]] = list(set(ids))
00186 elif 'HEADER' in line:
00187 readheader = True
00188 elif readheader:
00189 if 'ENDSEC' in line:
00190 readheader = False
00191 else:
00192 header += line
00193
00194 return [entById, entsByName, header]
00195
00196 def parseLine(self, line):
00197 """
00198 Parse a line
00199 """
00200 m = IFCLINE_RE.search(line)
00201 if m:
00202 id, name, attrs = m.groups()
00203 id = id.strip()
00204 name = name.strip()
00205 attrs = attrs.strip()
00206 else:
00207 return False
00208
00209 return {"id": id, "name": name, "attributes": self.parseAttributes(name, attrs)}
00210
00211 def parseAttributes(self, ent_name, attrs_str):
00212 """
00213 Parse the attributes of a line
00214 """
00215 parts = []
00216 lastpos = 0
00217
00218 while lastpos < len(attrs_str):
00219 newpos = self.nextString(attrs_str, lastpos)
00220 parts.extend(self.parseAttribute(attrs_str[lastpos:newpos-1]))
00221 lastpos = newpos
00222
00223 schema_attributes = self.schema.getAttributes(ent_name)
00224
00225 assert len(schema_attributes) == len(parts), \
00226 "Expected %s attributes, got %s (entity: %s" % \
00227 (len(schema_attributes), len(parts), ent_name)
00228
00229 attribute_names = [a[0] for a in schema_attributes]
00230
00231 return dict(zip(attribute_names, parts))
00232
00233 def parseAttribute(self, attr_str):
00234 """
00235 Map a single attribute to a python type (recursively)
00236 """
00237 parts = []
00238 lastpos = 0
00239 while lastpos < len(attr_str):
00240 newpos = self.nextString(attr_str, lastpos)
00241 s = attr_str[lastpos:newpos-1]
00242 if (s[0] == "(" and s[-1] == ")"):
00243 parts.append(self.parseAttribute(s[1:-1]))
00244 else:
00245 try:
00246 parts.append(float(s))
00247 except ValueError:
00248 if s[0] == "'" and s[-1] == "'":
00249 parts.append(s[1:-1])
00250 elif s == "$":
00251 parts.append(None)
00252 else:
00253 parts.append(s)
00254
00255 lastpos = newpos
00256
00257 return parts
00258
00259
00260 def nextString(self, s, start):
00261 """
00262 Parse the data part of a line
00263 """
00264 parens = 0
00265 quotes = 0
00266
00267 for pos in range(start,len(s)):
00268 c = s[pos]
00269 if c == "," and parens == 0 and quotes == 0:
00270 return pos+1
00271 elif c == "(" and quotes == 0:
00272 parens += 1
00273 elif c == ")" and quotes == 0:
00274 parens -= 1
00275 elif c == "\'" and quotes == 0:
00276 quotes = 1
00277 elif c =="\'" and quotes == 1:
00278 quotes = 0
00279
00280 return len(s)+1
00281
00282 class IfcEntity:
00283 "a container for an IFC entity"
00284 def __init__(self,ent,doc=None):
00285 self.data = ent
00286 self.id = int(ent['id'])
00287 self.type = ent['name'].upper().strip(",[]()")
00288 self.attributes = ent['attributes']
00289 self.doc = doc
00290
00291 def __repr__(self):
00292 return str(self.id) + ' : ' + self.type + ' ' + str(self.attributes)
00293
00294 def getProperty(self,propName):
00295 "finds the value of the given property or quantity in this object, if exists"
00296 propsets = self.doc.find('IFCRELDEFINESBYPROPERTIES','RelatedObjects',self)
00297 if not propsets: return None
00298 propset = []
00299 for p in propsets:
00300 if hasattr(p.RelatingPropertyDefinition,"HasProperties"):
00301 propset.extend(p.RelatingPropertyDefinition.HasProperties)
00302 elif hasattr(p.RelatingPropertyDefinition,"Quantities"):
00303 propset.extend(p.RelatingPropertyDefinition.Quantities)
00304 for prop in propset:
00305 if prop.Name == propName:
00306 print "found valid",prop
00307 if hasattr(prop,"LengthValue"):
00308 return prop.LengthValue
00309 elif hasattr(prop,"AreaValue"):
00310 return prop.AreaValue
00311 elif hasattr(prop,"VolumeValue"):
00312 return prop.VolumeValue
00313 elif hasattr(prop,"NominalValue"):
00314 return prop.NominalValue
00315 return None
00316
00317 def getAttribute(self,attr):
00318 "returns the value of the given attribute, if exists"
00319 if hasattr(self,attr):
00320 return self.__dict__[attr]
00321 return None
00322
00323 class IfcDocument:
00324 "an object representing an IFC document"
00325 def __init__(self,filename,schema="IFC2X3_TC1.exp",debug=False):
00326 DEBUG = debug
00327 f = IfcFile(filename,schema)
00328 self.filename = filename
00329 self.data = f.entById
00330 self.Entities = {0:f.header}
00331 for k,e in self.data.iteritems():
00332 eid = int(e['id'])
00333 self.Entities[eid] = IfcEntity(e,self)
00334 if DEBUG: print len(self.Entities),"entities created. Creating attributes..."
00335 for k,ent in self.Entities.iteritems():
00336 if DEBUG: print "attributing entity ",ent
00337 if hasattr(ent,"attributes"):
00338 for k,v in ent.attributes.iteritems():
00339 if DEBUG: print "parsing attribute: ",k," value ",v
00340 if isinstance(v,str):
00341 val = self.__clean__(v)
00342 elif isinstance(v,list):
00343 val = []
00344 for item in v:
00345 if isinstance(item,str):
00346 val.append(self.__clean__(item))
00347 else:
00348 val.append(item)
00349 else:
00350 val = v
00351 setattr(ent,k.strip(),val)
00352 if DEBUG: print "Document successfully created"
00353
00354 def __clean__(self,value):
00355 "turns an attribute value into something usable"
00356 try:
00357 val = value.strip(" ()'")
00358 if val[:3].upper() == "IFC":
00359 if "IFCTEXT" in val.upper():
00360 l = val.split("'")
00361 if len(l) == 3: val = l[1]
00362 elif "IFCBOOLEAN" in value.upper():
00363 l = val.split(".")
00364 if len(l) == 3: val = l[1]
00365 if val.upper() == "F": val = False
00366 elif val.upper() == "T": val = True
00367 elif "IFCREAL" in val.upper():
00368 l = val.split("(")
00369 if len(l) == 2: val = float(l[1].strip(")"))
00370 else:
00371 if '#' in val:
00372 if "," in val:
00373 val = val.split(",")
00374 l = []
00375 for subval in val:
00376 if '#' in subval:
00377 s = subval.strip(" #")
00378 if DEBUG: print "referencing ",s," : ",self.getEnt(int(s))
00379 l.append(self.getEnt(int(s)))
00380 val = l
00381 else:
00382 val = val.strip()
00383 val = val.replace("#","")
00384 if DEBUG: print "referencing ",val," : ",self.getEnt(int(val))
00385 val = self.getEnt(int(val))
00386 if not val:
00387 val = value
00388 except:
00389 if DEBUG: print "error parsing attribute",value
00390 val = value
00391 return val
00392
00393 def __repr__(self):
00394 return "IFC Document: " + self.filename + ', ' + str(len(self.Entities)) + " entities "
00395
00396 def getEnt(self,ref):
00397 "gets an entity by id number, or a list of entities by type"
00398 if isinstance(ref,int):
00399 if ref in self.Entities:
00400 return self.Entities[ref]
00401 elif isinstance(ref,str):
00402 l = []
00403 ref = ref.upper()
00404 for k,ob in self.Entities.iteritems():
00405 if hasattr(ob,"type"):
00406 if ob.type == ref:
00407 l.append(ob)
00408 return l
00409 return None
00410
00411 def search(self,pat):
00412 "searches entities types for partial match"
00413 l = []
00414 pat = pat.upper()
00415 for k,ob in self.Entities.iteritems():
00416 if hasattr(ob,"type"):
00417 if pat in ob.type:
00418 if not ob.type in l:
00419 l.append(ob.type)
00420 return l
00421
00422 def find(self,pat1,pat2=None,pat3=None):
00423 '''finds objects in the current IFC document.
00424 arguments can be of the following form:
00425 - (pattern): returns object types matching the given pattern (same as search)
00426 - (type,property,value): finds, in all objects of type "type", those whose
00427 property "property" has the given value
00428 '''
00429 if pat3:
00430 bobs = self.getEnt(pat1)
00431 obs = []
00432 for bob in bobs:
00433 if hasattr(bob,pat2):
00434 if bob.getAttribute(pat2) == pat3:
00435 obs.append(bob)
00436 return obs
00437 elif pat1:
00438 ll = self.search(pat1)
00439 obs = []
00440 for l in ll:
00441 obs.extend(self.getEnt(l))
00442 return obs
00443 return None
00444
00445 if __name__ == "__main__":
00446 print __doc__
00447