dxfReader.py

Go to the documentation of this file.
00001 """This module provides a function for reading dxf files and parsing them into a useful tree of objects and data.
00002 
00003         The convert function is called by the readDXF fuction to convert dxf strings into the correct data based
00004         on their type code.  readDXF expects a (full path) file name as input.
00005 """
00006 
00007 # --------------------------------------------------------------------------
00008 # DXF Reader v0.9 by Ed Blake (AKA Kitsu)
00009 #  2008.05.08 modif.def convert() by Remigiusz Fiedler (AKA migius)
00010 # --------------------------------------------------------------------------
00011 # ***** BEGIN GPL LICENSE BLOCK *****
00012 #
00013 # This program is free software; you can redistribute it and/or
00014 # modify it under the terms of the GNU General Public License
00015 # as published by the Free Software Foundation; either version 2
00016 # of the License, or (at your option) any later version.
00017 #
00018 # This program is distributed in the hope that it will be useful,
00019 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00020 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00021 # GNU General Public License for more details.
00022 #
00023 # You should have received a copy of the GNU General Public License
00024 # along with this program; if not, write to the Free Software Foundation,
00025 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00026 #
00027 # ***** END GPL LICENCE BLOCK *****
00028 # --------------------------------------------------------------------------
00029 
00030 
00031 from dxfImportObjects import *
00032 
00033 class Object:
00034         """Empty container class for dxf objects"""
00035 
00036         def __init__(self, _type='', block=False):
00037                 """_type expects a string value."""
00038                 self.type = _type
00039                 self.name = ''
00040                 self.data = []
00041 
00042         def __str__(self):
00043                 if self.name:
00044                         return self.name
00045                 else:
00046                         return self.type
00047 
00048         def __repr__(self):
00049                 return str(self.data)
00050 
00051         def get_type(self, kind=''):
00052                 """Despite the name, this method actually returns all objects of type 'kind' from self.data."""
00053                 if type:
00054                         objects = []
00055                         for item in self.data:
00056                                 if type(item) != list and item.type == kind:
00057                                         # we want this type of object
00058                                         objects.append(item)
00059                                 elif type(item) == list and item[0] == kind:
00060                                         # we want this type of data
00061                                         objects.append(item[1])
00062                         return objects
00063 
00064 
00065 class InitializationError(Exception): pass
00066 
00067 class StateMachine:
00068         """(finite) State Machine from the great David Mertz's great Charming Python article."""
00069 
00070         def __init__(self):
00071                 self.handlers = []
00072                 self.startState = None
00073                 self.endStates = []
00074 
00075         def add_state(self, handler, end_state=0):
00076                 """All states and handlers are functions which return
00077                 a state and a cargo."""
00078                 self.handlers.append(handler)
00079                 if end_state:
00080                         self.endStates.append(handler)
00081         def set_start(self, handler):
00082                 """Sets the starting handler function."""
00083                 self.startState = handler
00084 
00085 
00086         def run(self, cargo=None):
00087                 if not self.startState:
00088                         raise InitializationError,\
00089                                   "must call .set_start() before .run()"
00090                 if not self.endStates:
00091                         raise InitializationError, \
00092                                   "at least one state must be an end_state"
00093                 handler = self.startState
00094                 while 1:
00095                         (newState, cargo) = handler(cargo)
00096                         #print cargo
00097                         if newState in self.endStates:
00098                                 return newState(cargo)
00099                                 #break
00100                         elif newState not in self.handlers:
00101                                 raise RuntimeError, "Invalid target %s" % newState
00102                         else:
00103                                 handler = newState
00104 
00105 def get_name(data):
00106         """Get the name of an object from its object data.
00107 
00108         Returns a pair of (data_item, name) where data_item is the list entry where the name was found
00109         (the data_item can be used to remove the entry from the object data).  Be sure to check
00110         name not None before using the returned values!
00111         """
00112         value = None
00113         for item in data:
00114                 if item[0] == 2:
00115                         value = item[1]
00116                         break
00117         return item, value
00118 
00119 def get_layer(data):
00120         """Expects object data as input.
00121 
00122         Returns (entry, layer_name) where entry is the data item that provided the layer name.
00123         """
00124         value = None
00125         for item in data:
00126                 if item[0] == 8:
00127                         value = item[1]
00128                         break
00129         return item, value
00130 
00131 
00132 def convert(code, value):
00133         """Convert a string to the correct Python type based on its dxf code.
00134         code types:
00135                 ints = 60-79, 170-179, 270-289, 370-389, 400-409, 1060-1070
00136                 longs = 90-99, 420-429, 440-459, 1071
00137                 floats = 10-39, 40-59, 110-139, 140-149, 210-239, 460-469, 1010-1059
00138                 hex = 105, 310-379, 390-399
00139                 strings = 0-9, 100, 102, 300-309, 410-419, 430-439, 470-479, 999, 1000-1009
00140         """
00141         if 59 < code < 80 or 169 < code < 180 or 269 < code < 290 or 369 < code < 390 or 399 < code < 410 or 1059 < code < 1071:
00142                 value = int(float(value))
00143         elif 89 < code < 100 or 419 < code < 430 or 439 < code < 460 or code == 1071:
00144                 value = long(float(value))
00145         elif 9 < code < 60 or 109 < code < 150 or 209 < code < 240 or 459 < code < 470 or 1009 < code < 1060:
00146                 value = float(value)
00147         elif code == 105 or 309 < code < 380 or 389 < code < 400:
00148                 value = int(value, 16) # should be left as string?
00149         else: # it's already a string so do nothing
00150                 pass
00151         return value
00152 
00153 
00154 def findObject(infile, kind=''):
00155         """Finds the next occurance of an object."""
00156         obj = False
00157         while 1:
00158                 line = infile.readline()
00159                 if not line: # readline returns '' at eof
00160                         return False
00161                 if not obj: # We're still looking for our object code
00162                         if line.lower().strip() == '0':
00163                                 obj = True # found it
00164                 else: # we are in an object definition
00165                         if kind: # if we're looking for a particular kind
00166                                 if line.lower().strip() == kind:
00167                                         obj = Object(line.lower().strip())
00168                                         break
00169                         else: # otherwise take anything non-numeric
00170                                 if line.lower().strip() not in string.digits:
00171                                         obj = Object(line.lower().strip())
00172                                         break
00173                         obj = False # whether we found one or not it's time to start over
00174         return obj
00175 
00176 def handleObject(infile):
00177         """Add data to an object until end of object is found."""
00178         line = infile.readline()
00179         if line.lower().strip() == 'section':
00180                 return 'section' # this would be a problem
00181         elif line.lower().strip() == 'endsec':
00182                 return 'endsec' # this means we are done with a section
00183         else: # add data to the object until we find a new object
00184                 obj = Object(line.lower().strip())
00185                 obj.name = obj.type
00186                 done = False
00187                 data = []
00188                 while not done:
00189                         line = infile.readline()
00190                         if not data:
00191                                 if line.lower().strip() == '0':
00192                                         #we've found an object, time to return
00193                                         return obj
00194                                 else:
00195                                         # first part is always an int
00196                                         data.append(int(line.lower().strip()))
00197                         else:
00198                                 data.append(convert(data[0], line.strip()))
00199                                 obj.data.append(data)
00200                                 data = []
00201 
00202 def handleTable(table, infile):
00203         """Special handler for dealing with nested table objects."""
00204         item, name = get_name(table.data)
00205         if name: # We should always find a name
00206                 table.data.remove(item)
00207                 table.name = name.lower()
00208         # This next bit is from handleObject
00209         # handleObject should be generalized to work with any section like object
00210         while 1:
00211                 obj = handleObject(infile)
00212                 if obj.type == 'table':
00213                         print "Warning: previous table not closed!"
00214                         return table
00215                 elif obj.type == 'endtab':
00216                         return table # this means we are done with the table
00217                 else: # add objects to the table until one of the above is found
00218                         table.data.append(obj)
00219 
00220 
00221 
00222 
00223 def handleBlock(block, infile):
00224         """Special handler for dealing with nested table objects."""
00225         item, name = get_name(block.data)
00226         if name: # We should always find a name
00227                 # block.data.remove(item)
00228                 block.name = name
00229         # This next bit is from handleObject
00230         # handleObject should be generalized to work with any section like object
00231         while 1:
00232                 obj = handleObject(infile)
00233                 if obj.type == 'block':
00234                         print "Warning: previous block not closed!"
00235                         return block
00236                 elif obj.type == 'endblk':
00237                         return block # this means we are done with the table
00238                 else: # add objects to the table until one of the above is found
00239                         block.data.append(obj)
00240 
00241 
00242 
00243 
00244 """These are the states/functions used in the State Machine.
00245 states:
00246  start - find first section
00247  start_section - add data, find first object
00248    object - add obj-data, watch for next obj (called directly by start_section)
00249  end_section - look for next section or eof
00250  end - return results
00251 """
00252 
00253 def start(cargo):
00254         """Expects the infile as cargo, initializes the cargo."""
00255         #print "Entering start state!"
00256         infile = cargo
00257         drawing = Object('drawing')
00258         section = findObject(infile, 'section')
00259         if section:
00260                 return start_section, (infile, drawing, section)
00261         else:
00262                 return error, (infile, "Failed to find any sections!")
00263 
00264 def start_section(cargo):
00265         """Expects [infile, drawing, section] as cargo, builds a nested section object."""
00266         #print "Entering start_section state!"
00267         infile = cargo[0]
00268         drawing = cargo[1]
00269         section = cargo[2]
00270         # read each line, if it is an object declaration go to object mode
00271         # otherwise create a [index, data] pair and add it to the sections data.
00272         done = False
00273         data = []
00274         while not done:
00275                 line = infile.readline()
00276 
00277                 if not data: # if we haven't found a dxf code yet
00278                         if line.lower().strip() == '0':
00279                                 # we've found an object
00280                                 while 1: # no way out unless we find an end section or a new section
00281                                         obj = handleObject(infile)
00282                                         if obj == 'section': # shouldn't happen
00283                                                 print "Warning: failed to close previous section!"
00284                                                 return end_section, (infile, drawing)
00285                                         elif obj == 'endsec': # This section is over, look for the next
00286                                                 drawing.data.append(section)
00287                                                 return end_section, (infile, drawing)
00288                                         elif obj.type == 'table': # tables are collections of data
00289                                                 obj = handleTable(obj, infile) # we need to find all there contents
00290                                                 section.data.append(obj) # before moving on
00291                                         elif obj.type == 'block': # the same is true of blocks
00292                                                 obj = handleBlock(obj, infile) # we need to find all there contents
00293                                                 section.data.append(obj) # before moving on
00294                                         else: # found another sub-object
00295                                                 section.data.append(obj)
00296                         else:
00297                                 data.append(int(line.lower().strip()))
00298                 else: # we have our code, now we just need to convert the data and add it to our list.
00299                         data.append(convert(data[0], line.strip()))
00300                         section.data.append(data)
00301                         data = []
00302 def end_section(cargo):
00303         """Expects (infile, drawing) as cargo, searches for next section."""
00304         #print "Entering end_section state!"
00305         infile = cargo[0]
00306         drawing = cargo[1]
00307         section = findObject(infile, 'section')
00308         if section:
00309                 return start_section, (infile, drawing, section)
00310         else:
00311                 return end, (infile, drawing)
00312 
00313 def end(cargo):
00314         """Expects (infile, drawing) as cargo, called when eof has been reached."""
00315         #print "Entering end state!"
00316         infile = cargo[0]
00317         drawing = cargo[1]
00318         #infile.close()
00319         return drawing
00320 
00321 def error(cargo):
00322         """Expects a (infile, string) as cargo, called when there is an error during processing."""
00323         #print "Entering error state!"
00324         infile = cargo[0]
00325         err = cargo[1]
00326         infile.close()
00327         print "There has been an error:"
00328         print err
00329         return False
00330 
00331 def readDXF(filename):
00332         """Given a file name try to read it as a dxf file.
00333 
00334         Output is an object with the following structure
00335         drawing
00336                 header
00337                         header data
00338                 classes
00339                         class data
00340                 tables
00341                         table data
00342                 blocks
00343                         block data
00344                 entities
00345                         entity data
00346                 objects
00347                         object data
00348         where foo data is a list of sub-objects.  True object data
00349         is of the form [code, data].
00350 """
00351         infile = open(filename)
00352 
00353         sm = StateMachine()
00354         sm.add_state(error, True)
00355         sm.add_state(end, True)
00356         sm.add_state(start_section)
00357         sm.add_state(end_section)
00358         sm.add_state(start)
00359         sm.set_start(start)
00360         try:
00361                 drawing = sm.run(infile)
00362                 if drawing:
00363                         drawing.name = filename
00364                         for obj in drawing.data:
00365                                 item, name = get_name(obj.data)
00366                                 if name:
00367                                         obj.data.remove(item)
00368                                         obj.name = name.lower()
00369                                         setattr(drawing, name.lower(), obj)
00370                                         # Call the objectify function to cast
00371                                         # raw objects into the right types of object
00372                                         obj.data = objectify(obj.data)
00373                                 #print obj.name
00374         finally:
00375                 infile.close()
00376         return drawing
00377 if __name__ == "__main__":
00378         filename = r".\examples\block-test.dxf"
00379         drawing = readDXF(filename)
00380         for item in drawing.entities.data:
00381                 print item

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