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
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
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
00058 objects.append(item)
00059 elif type(item) == list and item[0] == kind:
00060
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
00097 if newState in self.endStates:
00098 return newState(cargo)
00099
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)
00149 else:
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:
00160 return False
00161 if not obj:
00162 if line.lower().strip() == '0':
00163 obj = True
00164 else:
00165 if kind:
00166 if line.lower().strip() == kind:
00167 obj = Object(line.lower().strip())
00168 break
00169 else:
00170 if line.lower().strip() not in string.digits:
00171 obj = Object(line.lower().strip())
00172 break
00173 obj = False
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'
00181 elif line.lower().strip() == 'endsec':
00182 return 'endsec'
00183 else:
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
00193 return obj
00194 else:
00195
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:
00206 table.data.remove(item)
00207 table.name = name.lower()
00208
00209
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
00217 else:
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:
00227
00228 block.name = name
00229
00230
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
00238 else:
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
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
00267 infile = cargo[0]
00268 drawing = cargo[1]
00269 section = cargo[2]
00270
00271
00272 done = False
00273 data = []
00274 while not done:
00275 line = infile.readline()
00276
00277 if not data:
00278 if line.lower().strip() == '0':
00279
00280 while 1:
00281 obj = handleObject(infile)
00282 if obj == 'section':
00283 print "Warning: failed to close previous section!"
00284 return end_section, (infile, drawing)
00285 elif obj == 'endsec':
00286 drawing.data.append(section)
00287 return end_section, (infile, drawing)
00288 elif obj.type == 'table':
00289 obj = handleTable(obj, infile)
00290 section.data.append(obj)
00291 elif obj.type == 'block':
00292 obj = handleBlock(obj, infile)
00293 section.data.append(obj)
00294 else:
00295 section.data.append(obj)
00296 else:
00297 data.append(int(line.lower().strip()))
00298 else:
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
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
00316 infile = cargo[0]
00317 drawing = cargo[1]
00318
00319 return drawing
00320
00321 def error(cargo):
00322 """Expects a (infile, string) as cargo, called when there is an error during processing."""
00323
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
00371
00372 obj.data = objectify(obj.data)
00373
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