<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/env python

import os
import sys
import string
import time
from stl import mesh

# This file could use the library xml.etree.ElementTree but doesn't.  The xml is explicitly written so its ordered and pretty.


class FebioElement:
	def __init__(self, material):
		self.mat = material
		self.type = None
		self.connect = {}		# contains matched pairs of id# and element connectivity
		self.nodes=set()

	def __repr__(self):
		return "&lt;FebioElement type:{} mat:{} connect:{} elements&gt;".format(self.type, self.mat, len(self.connect))

nodeData={}
elemData={}
physGroups={}	# physGroups{PG#] = "name in mesh"
previousNodes = 0	# number of nodes in all previous meshes
contactGroups = {}

inputDirectory = '/home/landisb/workspace/IndentationTests/Leg/FeBio/BasicLeg'

os.chdir(inputDirectory)
for file in os.listdir(inputDirectory):
	if file.endswith('.msh'):
		gmshMapping = {'1': 'line', '2': 'tri3', '3': 'quad4', '4': 'tet4',
					   '5': 'hex8', '8': 'line3', '9': 'tri6', '11': 'tet10',
					   '12': 'hex27', '15': 'point', '16': 'quad8', '17': 'hex20'}
		meshFile = open(file, 'r')
		print 'Parsing Mesh {}'.format(file)
		pN=previousNodes
		#
		line = meshFile.readline()
		while line:
			if line.startswith('$Nodes') or line.startswith('$NOD'):
				#~ print 'Reading Nodes...'
				nodes = int( meshFile.readline() )
				for i in xrange(nodes):
					line=meshFile.readline().split()	#readline and split by whitespace
					nodeData[int(line[0])+pN]= tuple(float(i) for i in line[-3:])		# mesh node number + previousNodes
				previousNodes+=nodes
			elif line.upper().startswith('$EL'):
				#~ print 'Reading Elements...'
				if line.startswith('$Elements'):	pg = 3	# pg is the index of the physical group line
				elif line.startswith('$ELM'):		pg = 2
					#~ try:	elemData[line[2]].connect[int(line[0])] = [int(j)+pN for j in line[-nodeNum:]]
					#~ except KeyError:
						#~ f='"'+file.split('.')[0]
						#~ if nodeNum == 3:
							#~ f = f+'_surf_'+line[pg]
						#~ elemData[line[2]] = FebioElement(line[2], f+'"' )
						#~ elemData[line[2]].connect[int(line[0])] = [int(j)+pN for j in line[-nodeNum:]]
				elements = int( meshFile.readline() )
				for i in xrange(elements):
					line=meshFile.readline().split()	#readline and split by whitespace
					mapping = gmshMapping[line[1]]
					name = physGroups[line[pg]]
					#
					# #		Good spot for checking elemData is initialized
					#
					nodeNum = int(mapping.strip(string.ascii_letters))  # number of nodes in the element
					if   mapping.startswith('line'):	print 'Lines are currently ignored.'
					elif mapping.startswith('point'):	print 'Points are currently ignored.'
					elif (mapping.startswith('quad') or mapping.startswith('hex') or
						mapping.startswith('tri') or mapping.startswith('tet') ):
						elemData[name].connect[int(line[0])] = [int(j)+pN for j in line[-nodeNum:]]
						elemData[name].type=mapping
						elemData[name].nodes.update( int(j)+pN for j in line[-nodeNum:] )
						if mapping is'tet10':	# gmsh and febio second order tetrahedra switch the ordering of nodes 9 and 10
							c = elemData[name].connect[int(line[0])]
							c[-2],c[-1] = c[-1],c[-2]		# swap last two elements in connectivity for tet10
					else:				print 'Element type {} unaccounted for'.format(line[1])
			elif line.startswith('$PhysicalNames'):
				names = int( meshFile.readline() )
				for i in xrange(names):
					line=meshFile.readline().split()
					# line[0]	dimensionality of group as a string
					# line[1]	physical group # as a string
					# line[2]	name as a string already in quotes
					physGroups[line[1]] = line[2]
					#   Populate FebioElementData here
					elemData[line[2]] = FebioElement(line[1])
			line=meshFile.readline()
		meshFile.close()
	elif file.endswith('.stl'):
		stlMaterialNumber = '4'			# for material assignment in FeBio
		pN = previousNodes + 1		# +1 beacuse python list start at 0 and the index will be added
		myMesh = mesh.Mesh.from_file(file)
		pts = set()
		for p in myMesh.points:
			pts.add(tuple(p[:3]))
			pts.add(tuple(p[6:]))
			pts.add(tuple(p[3:6]))
		#
		points = list(pts)
		previousNodes += len(points)
		#
		for id, p in enumerate(points):
			nodeData[pN+id] = p  	# mesh node number + previousNodes
		#
		name = '"{}"'.format(file.split('/')[-1])
		elemData[name] = FebioElement(stlMaterialNumber)
		physGroups[stlMaterialNumber] = name
		for e, p in enumerate(myMesh.points):
			v1, v2, v3 = tuple(p[:3]), tuple(p[6:]), tuple(p[3:6])  # stl vertices
			connect = (pN+points.index(v1), pN+points.index(v2), pN+points.index(v3))
			elemData[name].connect[pN+e] = connect
			elemData[name].type = 'tri3'
	elif os.path.isdir(file):		pass
	elif file.endswith('._msh' ):	pass
	elif file.endswith('.py'  ):	pass
	elif file.endswith('.feb' ):	pass
	elif file.endswith('.xplt'):	pass
	elif file.endswith('.png' ):	pass
	elif file.endswith('.geo' ):	pass
	elif file.endswith('.txt' ):	pass
	elif file.endswith('.log' ):	pass
	elif file.endswith('.prv' ):	pass
	elif file.endswith('.dat' ):	pass
	else:	print 'This file does not handle {}'.format(file)


outFile = myDirectory.split('/')[-1]+'.feb'
output = open(outFile, 'w')
print '\n', 'Converting...'


for name in sorted(elemData):
	if 'contact' in name:
		contactName = name.replace('surf_', '')
		otherSurf = name.rsplit('_', 1)[0]+'"'
		if len(elemData[name].connect) &lt;= len(elemData[otherSurf].connect):
			contactGroups[contactName]=(name, otherSurf)
		else:contactGroups[contactName]=(otherSurf, name)
	elif '.stl' in name:
		newName = name.split('.')[0] + '_surf"'
		contactName = '"contact_'+name[1:]
		otherSurf  = '"skin_surf"'
		if len(elemData[name].connect) &lt;= len(elemData[otherSurf].connect):
			contactGroups[contactName]=(newName, otherSurf)
		else:contactGroups[contactName]=(otherSurf, newName)


output.write('&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;\n')
output.write('&lt;!-- This document was written by\n	{}\n	on {}--&gt;\n'.format(sys.argv[0], time.strftime('%b %d %Y at %H:%m:%S %p') ))
output.write('&lt;febio_spec version="2.0"&gt;\n')

# Material
output.write('	&lt;Material&gt;\n')
for name,e in sorted(elemData.iteritems(), key=lambda eD: eD[1].mat):	# sort by material
	if 'surf' in name	or	'contact' in name	or	'Fixed' in name:	pass
	elif 'Probe' in name or 	'Rigid' in name:
		output.write('		&lt;material id="{}" name={} type="rigid body"&gt;\n'.format(e.mat, name))
		output.write('			&lt;density&gt;2.8e-09&lt;/density&gt;\n')
		output.write('		&lt;/material&gt;\n')
	else:
		output.write('		&lt;material id="{}" name={} type="neo-Hookean"&gt;\n'.format(e.mat ,name))
		# How should I handle setting the material parameters?
		output.write('			&lt;density&gt;1e-09&lt;/density&gt;\n')
		output.write('			&lt;E&gt;100&lt;/E&gt;\n')
		output.write('			&lt;v&gt;0.3&lt;/v&gt;\n')
		output.write('		&lt;/material&gt;\n')
output.write('	&lt;/Material&gt;\n')

# Geometry
output.write('	&lt;Geometry&gt;\n')
output.write('		&lt;Nodes&gt;\n')
for n in nodeData:	output.write('			&lt;node id="{}"&gt; {: 10.7e}, {: 10.7e}, {: 10.7e} &lt;/node&gt;\n'.format(n, *nodeData[n]) )
output.write('		&lt;/Nodes&gt;\n')
for name, e in sorted(elemData.iteritems()):		# NodeSets,	seperate loops for pretty xml
	if 'bone' in name or 'Symmetry' in name or 'Fixed' in name:
		output.write('		&lt;NodeSet name={}&gt;\n'.format(name))
		for id in sorted(e.nodes):	output.write('			&lt;node id="{}"/&gt;\n'.format(id) )
		output.write('		&lt;/NodeSet&gt;\n')
for name,e in sorted(elemData.iteritems(), key=lambda eD: eD[1].mat):	# sort by material number
	# FeBio does not require different element to have unique elem ids
	if not e.type:	raw_input('None type found')
	if (    e.type.startswith('tet') or
		 e.type.startswith('hex') or
		(e.type.startswith('tri') and '.stl' in name) ):
			output.write('		&lt;Elements type="{}" mat="{}" name={}&gt;\n'.format(e.type, e.mat, name))
			for id, c in sorted(e.connect.iteritems()):    output.write('			&lt;elem id="{}"&gt; {} &lt;/elem&gt;\n'.format(id, str(c)[1:-1]))
			output.write('		&lt;/Elements&gt;\n')
for name, e in sorted(elemData.iteritems()):		# Surfaces,	seperate loops for pretty xml
	if not e.type:raw_input('None type found')
	if e.type.startswith('tri') or e.type.startswith('quad'):
		if '_surf' in name:
			output.write('		&lt;Surface name={}&gt;\n'.format(name))
			for id, c in sorted(e.connect.iteritems()):	output.write('			&lt;{} id="{}"&gt; {} &lt;/{}&gt;\n'.format( e.type, id, str(c)[1:-1], e.type ))
			output.write('		&lt;/Surface&gt;\n')
		elif '.stl' in name:
			output.write('		&lt;Surface name={}&gt;\n'.format(name.split('.')[0]+'_surf"'))
			for id, c in sorted(e.connect.iteritems()):	output.write('			&lt;{} id="{}"&gt; {} &lt;/{}&gt;\n'.format(e.type, id, str(c)[1:-1], e.type))
			output.write('		&lt;/Surface&gt;\n')
# ElementData?
output.write('	&lt;/Geometry&gt;\n')

# Boundary
#	Repeat fix as necessary (options: x, y, z displacement and u, v, w rotation on x, y, z)
output.write('	&lt;Boundary&gt;\n')
for name in elemData:
	if 'bone' in name:			output.write('		&lt;fix bc="xyz" set={}/&gt;\n'.format(name) )
	elif 'Symmetry' in name:		output.write('		&lt;fix bc="{}" set={}/&gt;\n'.format(name[1], name) )
	elif 'Fixed' in name:		output.write('		&lt;fix bc={}" set={}/&gt;\n'.format(name.split('Fixed')[0], name) )
	# elif 'Fixed' in name:		output.write('		&lt;fix bc={} set={}/&gt;\n'.format(name.replace('Fixed', ''), name))
output.write('	&lt;/Boundary&gt;\n')

#Initial Constraints
output.write('	&lt;Constraints&gt;\n')
for name,e in sorted(elemData.iteritems(), key=lambda eD: eD[1].mat):	# sort by material
	if 'Probe' in name or 	'Rigid' in name:
		output.write('		&lt;rigid_body mat="{}"&gt;\n'.format(e.mat))
		output.write('			&lt;fixed bc="x"/&gt;\n')
		output.write('			&lt;fixed bc="y"/&gt;\n')
		output.write('			&lt;fixed bc="z"/&gt;\n')
		output.write('			&lt;fixed bc="Rx"/&gt;\n')
		output.write('			&lt;fixed bc="Ry"/&gt;\n')
		output.write('			&lt;fixed bc="Rz"/&gt;\n')
		output.write('		&lt;/rigid_body&gt;\n')
output.write('	&lt;/Constraints&gt;\n')

# Contact
output.write('	&lt;Contact&gt;\n')
for name, cG in contactGroups.iteritems():
	if 'stl' in name:
		output.write('		&lt;contact type="facet-to-facet sliding" name={}&gt;\n'.format(name) )
		output.write('			&lt;laugon&gt;1&lt;/laugon&gt;\n')
		output.write('			&lt;penalty&gt;10&lt;/penalty&gt;\n')
		output.write('			&lt;maxaug&gt;10&lt;/maxaug&gt;\n')
		output.write('			&lt;two_pass&gt;0&lt;/two_pass&gt;\n')
		output.write('			&lt;surface type="master" set={}/&gt;\n'.format(cG[0]) )
		output.write('			&lt;surface type="slave"  set={}/&gt;\n'.format(cG[1]) )
		output.write('		&lt;/contact&gt;\n')
	elif 'contact' in name:
		output.write('		&lt;contact type="sticky" name={}&gt;\n'.format(name) )		# sticky is tied but initial contact is not necessary!
		output.write('			&lt;laugon&gt;1&lt;/laugon&gt;\n')
		output.write('			&lt;penalty&gt;10&lt;/penalty&gt;\n')
		output.write('			&lt;maxaug&gt;10&lt;/maxaug&gt;\n')
		output.write('			&lt;surface type="master" set={}/&gt;\n'.format(cG[0]) )
		output.write('			&lt;surface type="slave"  set={}/&gt;\n'.format(cG[1]) )
		output.write('		&lt;/contact&gt;\n')
output.write('	&lt;/Contact&gt;\n')

# # Loads (i.e. gravity)
output.write('	&lt;Loads&gt;\n')
output.write('		&lt;body_load type = "const"&gt;\n')
output.write('			&lt;z lc="1"&gt; 1000 &lt;/z&gt;\n')
output.write('		&lt;/body_load &gt;\n')
output.write('	&lt;/Loads &gt;\n')

#Load Data
output.write('	&lt;LoadData&gt;\n')
output.write('		&lt;loadcurve id="1" type="smooth"&gt;\n')
output.write('			&lt;point&gt;0,0&lt;/point&gt;\n')
output.write('			&lt;point&gt;1,1&lt;/point&gt;\n')
output.write('		&lt;/loadcurve&gt;\n')
#
output.write('		&lt;loadcurve id="2" type="step"&gt;\n')
steps=50
for p in xrange(steps+1):
	output.write('			&lt;point&gt;{:4.2f},{}&lt;/point&gt;\n'.format(float(p)/steps,1.0/steps) )
output.write('		&lt;/loadcurve&gt;\n')
#
output.write('	&lt;/LoadData&gt;\n')

#Output requirements
output.write('	&lt;Output&gt;\n')
output.write('		&lt;plotfile type="febio"&gt;\n')
output.write('			&lt;var type="contact gap"/&gt;\n')
output.write('			&lt;var type="contact pressure"/&gt;\n')
output.write('			&lt;var type="contact traction"/&gt;\n')
output.write('			&lt;var type="displacement"/&gt;\n')
output.write('			&lt;var type="reaction forces"/&gt;\n')
output.write('			&lt;var type="stress"/&gt;\n')
output.write('		&lt;/plotfile&gt;\n')
output.write('	&lt;/Output&gt;\n')


# Step
output.write('	&lt;Step name="Step01"&gt;\n')
#
output.write('		&lt;Module type="solid"/&gt;\n')
#
output.write('		&lt;Control&gt;\n')
output.write('			&lt;time_steps&gt;{}&lt;/time_steps&gt;\n'.format(steps))		# required tag
output.write('			&lt;step_size&gt;{}&lt;/step_size&gt;\n'.format(1.0/steps))		# required tag
output.write('			&lt;time_stepper&gt;\n')
output.write('				&lt;dtmin&gt;1e-10&lt;/dtmin&gt;\n')
output.write('				&lt;dtmax lc="2"&gt;{}&lt;/dtmax&gt;\n'.format(1.0/steps))	# Load curve necessary for MustPoint printing
output.write('				&lt;max_retries&gt;25&lt;/max_retries&gt;\n')
output.write('				&lt;opt_iter&gt;10&lt;/opt_iter&gt;\n')
output.write('				&lt;aggressiveness&gt;1&lt;/aggressiveness&gt;\n')
output.write('			&lt;/time_stepper&gt;\n')
output.write('			&lt;optimize_bw&gt;1&lt;/optimize_bw&gt;\n')
output.write('			&lt;plot_level&gt;PLOT_MUST_POINTS&lt;/plot_level&gt;\n')
output.write('			&lt;analysis type="static"/&gt;\n')
output.write('		&lt;/Control&gt;\n')
#
output.write('		&lt;Constraints&gt;\n')
for name,e in sorted(elemData.iteritems(), key=lambda eD: eD[1].mat):	# sort by material
	if 'Probe' in name or 	'Rigid' in name:
		output.write('		&lt;rigid_body mat="{}"&gt;\n'.format(e.mat))
		#~ output.write('				&lt;prescribed bc="x" lc="1"&gt;-20&lt;/prescribed&gt;\n')
		output.write('				&lt;prescribed bc="y" lc="1"&gt;-30&lt;/prescribed&gt;\n')
		#~ output.write('				&lt;prescribed bc="z" lc="1"&gt;-35&lt;/prescribed&gt;\n')
		output.write('			&lt;/rigid_body&gt;\n')
output.write('		&lt;/Constraints&gt;\n')
#
output.write('	&lt;/Step&gt;\n')


output.write('&lt;/febio_spec&gt;\n')
output.close()

print 'FEBio XML written to', outFile
</pre></body></html>