#!/usr/bin/env python """Convert csv finite element data into Gmsh pos format. Usage: postcsv.py --mesh=gmshfile.msh --results=csvfile.csv [--average] If --average is specified, the data for each node ID and each element ID is averaged, otherwise repeated IDs are assumed to be new time steps. csvfile.csv is in the following format: # comments start with # # time step 1 nodal scalar and vector data node, heading1, heading2 1, 132.5, .0032 .0012 .0011 2, 100.2, .0030 .0004 -.0120 3, 98.3, .0021 .0032 .0002 # time step 1 elemental scalar data element, heading3 1, 32.63 2, 30.34 3, 21.32 # time step 2 nodal scalar and vector data node, heading1, heading2 1, 232.5, .0022 .0022 .0031 2, 200.2, .0020 .0014 .0110 3, 68.3, .0011 .0022 .0012 Carl Osterwisch , January 2005 """ import Gmsh import os, sys, re class PostMesh(Gmsh.Mesh): """Store and convert FE results into Gmsh pos format. Extends the Gmsh.Mesh class by adding the following attributes: Members: dataTypes -- dict to convert data length into type (scalar, vector, ...) elemTypes -- dict to convert mesh element type into pos element type posTypes -- list in pos defined order of pos object types nodalData -- dict of nodal results elementData -- dict of element centroid results Methods: csvparse(csvfile): Read a comma-separated-value file of nodal results makeviews(file): Write all the data out to a Gmsh pos file saveview(posfile): Called by makeviews to output the data for one view """ # dataTypes is used to translate the length of a vector into its # corresponding Gmsh type. dataTypes = {1:'S', 3:'V', 9:'T'} # elemTypes is used to convert from Gmsh mesh element type to Gmsh # post-processing object type. elemTypes = {1:'L', 2:'T', 3:'Q', 4:'S', 5:'H', 6:'I', 7:'Y'} # postTypes lists the valid Gmsh post-processing objects (dataType-elemType # combinations) in the order they are defined in the pos output file # format. postTypes = ['SP', 'VP', 'TP', 'SL', 'VL', 'TL', 'ST', 'VT', 'TT', 'SQ', 'VQ', 'TQ', 'SS', 'VS', 'TS', 'SH', 'VH', 'TH', 'SI', 'VI', 'TI', 'SY', 'VY', 'TY', 'T2', 'T2C', 'T3', 'T3C'] def __init__(self): Gmsh.Mesh.__init__(self) self.nodalData = {} #{ viewName: { nodeID: [ [time1data], [time2data] ] } } self.elementData = {} #{ viewName: { elemID: [ [time1data], [time2data] ] } } # Delimiter between columns in the data file self.separator = '[,;]' def csvparse(self, csvfile): """Parse ascii file of results into elementData and nodeData. The file should be comma separated columns with matching column headings at the top. The first column is assumed to be the node or element number. Each unique heading will generate a new view within Gmsh. Repeated headings will generate time steps for that view. """ csv = re.compile('\s*' + self.separator + '\s*') elem = re.compile('el', re.I) datatype = self.nodalData Gmsh.send('Parsing results from %s'%csvfile.name) headings = [] for line in csvfile: columns = csv.split(line.strip()) if len(columns) > 1: # There are at least two columns. linedata = [] try: id = int(columns[0]) linedata = [map(float, column.split()) for column in columns[1:]] except ValueError: # Assume this is a heading. headings = columns if elem.match(headings[0]): datatype = self.elementData else: datatype = self.nodalData else: for viewName, viewData in zip(headings[1:], linedata): if not datatype.has_key(viewName): datatype[viewName] = {} if not datatype[viewName].has_key(id): datatype[viewName][id] = [ viewData ] else: datatype[viewName][id].append(viewData) for (viewName, nodes) in self.nodalData.items(): Gmsh.send(' %s %d Nodal Results'%(viewName, len(nodes))) for (viewName, elems) in self.elementData.items(): Gmsh.send(' %s %d Elemental Results'%(viewName, len(elems))) def saveview(self, posfile, viewName=None, viewData=[]): """Store the given viewData to the posfile. If viewName is None it will output a header line to the file. """ if not viewName: print >>posfile, '$PostFormat\n1.2 0 8\n$EndPostFormat' elif len(viewData): Gmsh.send(' View '+viewName) nts = len(viewData.values()[0][0][1][0]) print >>posfile, '$View\n%s %d'%(viewName, nts) # Print section lengths. for type in self.postTypes: if viewData.has_key(type): print >>posfile, len(viewData[type]), Gmsh.send(' %s %d'%(type, len(viewData[type]))) else: print >>posfile, 0, # Print dummy time-step values. print >>posfile, '\n', ' '.join([str(i) for i in range(nts)]) # Print data. for type in self.postTypes: if viewData.has_key(type): for object in viewData[type]: for x in range(3): # Print the coordinates in the appropriate order. print >>posfile, ' '.join([str(i[x]) for i in object[0]]) for timestep in range(nts): for nodalResult in object[1]: # Print the results. try: print >>posfile, ' '.join([str(i) for i in nodalResult[timestep]]) except IndexError: Gmsh.send(' Insufficient time steps for all locations.', Gmsh.ERROR) Gmsh.send(' Consider using --average if this is one time step.', Gmsh.ERROR) print >>posfile, '$EndView' def makeviews(self, file): """Generate post-processing views for the results. 1. Loop through all defined elements and generate corresponding post-processor objects for elements with nodalData. 2. Loop through all elementData and generate corresponding post-processor objects. """ self.saveview(file) # This just prints the header information if len(self.nodalData): Gmsh.send('Saving nodal results to '+file.name) for viewName in self.nodalData.keys(): viewData = {} for elem in self.elements.values(): # Iterate over all defined elements. coord = [] result = [] for nid in elem[2]: # Gather node coordinates and results for this element. try: coord.append(self.nodes[nid]) except KeyError: Gmsh.send(' Mesh is missing node %d'%nid, Gmsh.ERROR) try: result.append(self.nodalData[viewName][nid]) except KeyError: pass if len(result) == len(coord) and \ self.elemTypes.has_key(elem[0]): # There was data available at every node for this # element and this element has a corresponding # post-processing object. if self.dataTypes.has_key(len(result[0][0])): postType = self.dataTypes[len(result[0][0])] + \ self.elemTypes[elem[0]] if not viewData.has_key(postType): viewData[postType] = [] viewData[postType].append( (coord, result) ) else: Gmsh.send(" No data type for "+repr(result[0]), Gmsh.ERROR) self.saveview(file, viewName, viewData) if len(self.elementData): Gmsh.send('Saving elemental results to %s'%file.name) for (viewName, data) in self.elementData.items(): viewData = {} for (eid, edata) in data.items(): try: elem = self.elements[eid] except KeyError: Gmsh.send(' Mesh is missing element %d'%eid, Gmsh.ERROR) else: coord = [] for nid in elem[2]: # Gather node coordinates and results for this # element. try: coord.append(self.nodes[nid]) except KeyError: Gmsh.send(' Mesh is missing node %d'%nid, Gmsh.ERROR) if len(elem[2]) == len(coord) and \ self.elemTypes.has_key(elem[0]): # There was data available at every node for this # element and this element has a corresponding # post-processing object. result = [edata]*len(coord) if self.dataTypes.has_key(len(result[0][0])): postType = (self.dataTypes[len(result[0][0])] + self.elemTypes[elem[0]]) if not viewData.has_key(postType): viewData[postType] = [] viewData[postType].append( (coord, result) ) else: Gmsh.send(" No data type for "+repr(result[0]), Gmsh.ERROR) self.saveview(file, viewName, viewData) ############################################################################### # Main program begins here ############################################################################### cmdline = Gmsh.getopt(sys.argv[1:]) if cmdline.has_key('--socket'): connection = Gmsh.Client(cmdline['--socket']) if cmdline.has_key('--help') or cmdline.has_key('-h'): print __doc__ elif cmdline.has_key('--options'): Gmsh.send('--noaverage', Gmsh.OPTION) Gmsh.send('--average', Gmsh.OPTION) else: if not cmdline.has_key('--results'): Gmsh.send("No results specified.", Gmsh.ERROR) sys.exit(1) if not cmdline.has_key('--mesh'): mshname = cmdline['--results'].split('.')[0] + '.msh' else: mshname = cmdline['--mesh'] try: mshfile = open(mshname) csvfile = open(cmdline['--results']) except: Gmsh.send(str(sys.exc_info()[1]), Gmsh.ERROR) else: pmesh = PostMesh() pmesh.read(mshfile) mshfile.close() pmesh.csvparse(csvfile) csvfile.close() if cmdline.has_key('--average'): Gmsh.send('Averaging data to one time step.') for result in [ pmesh.nodalData, pmesh.elementData ]: for view in result.keys(): for (id, data) in result[view].items(): nts = len(data) averaged = [] for col in range(min(map(len, data))): sum = 0.0 for d in data: sum += d[col] averaged.append(sum/nts) result[view][id] = [averaged] try: posfile = open(os.path.splitext(csvfile.name)[0]+'.pos', 'w') except: Gmsh.send(str(sys.exc_info()[1]), Gmsh.ERROR) else: pmesh.makeviews(posfile) posfile.close() Gmsh.send(posfile.name, Gmsh.MERGE_FILE)