ifcReader.py

Go to the documentation of this file.
00001 #***************************************************************************
00002 #*                                                                         *
00003 #*   Copyright (c) 2011                                                    *  
00004 #*   Yorik van Havre, Marijn van Aerle                                     *  
00005 #*                                                                         *
00006 #*   This program is free software; you can redistribute it and/or modify  *
00007 #*   it under the terms of the GNU General Public License (GPL)            *
00008 #*   as published by the Free Software Foundation; either version 2 of     *
00009 #*   the License, or (at your option) any later version.                   *
00010 #*   for detail see the LICENCE text file.                                 *
00011 #*                                                                         *
00012 #*   This program is distributed in the hope that it will be useful,       *
00013 #*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00014 #*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00015 #*   GNU Library General Public License for more details.                  *
00016 #*                                                                         *
00017 #*   You should have received a copy of the GNU Library General Public     *
00018 #*   License along with this program; if not, write to the Free Software   *
00019 #*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
00020 #*   USA                                                                   *
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         # Regexes must be greedy to prevent matching outer entity and end_entity strings
00106         # Regexes have re.DOTALL to match newlines
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             # find the shortest string matched from the end of the entity type header to the
00117             # first occurence of a NO_ATTR string (when it occurs on a new line)
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)  # id,name,attrs
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] == ")"): # list, recurse
00243                 parts.append(self.parseAttribute(s[1:-1]))
00244             else:
00245                 try:
00246                     parts.append(float(s)) # number, any kind
00247                 except ValueError:
00248                     if s[0] == "'" and s[-1] == "'": # string
00249                         parts.append(s[1:-1])
00250                     elif s == "$":
00251                         parts.append(None)
00252                     else:
00253                         parts.append(s) # ref, enum or other
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 

Generated on Wed Nov 23 19:00:18 2011 for FreeCAD by  doxygen 1.6.1