"""Convert unv mesh files to the fly format.
usage: tofly.py [-h] [-e DIMENSIONS] [UNV] [FLY]
Convert unv files to the fly format. Elements that belong to a group called
'contact' will be converted to their contact counterparts. First and secound
order meshes are supported.
positional arguments:
UNV Path to the input file or '-' for stdin. It must
already exist and be stored in the unv format. If
ommited stdin will be used instead.
FLY Path to the output file or '-' for stdout. Overridden
if it already exists. If ommited stdout will be used
instead.
optional arguments:
-h, --help show this help message and exit
-e DIMENSIONS, --exclude DIMENSIONS
Comma separated list of dimensions that shall be
ignored while converting (e.g. '-e 1,2' only converts
3D elements).
"""
import functools
import pathlib
CONTACT_GRP = "contact"
UNV_DELIM = " -1"
UNV_NODES = "2411"
UNV_ELEMS = "2412"
UNV_GROUPS = "2467"
UNV_BEAM = set(["11", "21", "22", "23", "24"])
UNV_index = {
1: set(["11", "21", "22", "24"]),
2: set(["41", "81", "91", "42", "82", "92", "44"]),
3: set(["115", "116", "111", "118"]),
}
FLY_MESH_NAME = "Mesh"
FLY_NODES = "Nodes"
FLY_LINE2 = "Line2"
FLY_LINE3 = "Line3"
FLY_TRI3 = "Tri3"
FLY_TRI6 = "Tri6"
FLY_REC4 = "Rec4"
FLY_REC8 = "Rec8"
FLY_REC9 = "Rec9"
FLY_TET4 = "Tet4"
FLY_TET10 = "Tet10"
FLY_HEX8 = "Hex8"
FLY_HEX20 = "Hex20"
FLY_HEX27 = "Hex27"
FLY_LINE2_CONTACT = "Line2_Contact"
FLY_LINE3_CONTACT = "Line3_Contact"
FLY_TRI3_CONTACT = "Tri3_Contact"
FLY_TRI6_CONTACT = "Tri6_Contact"
FLY_REC4_CONTACT = "Rec4_Contact"
FLY_REC8_CONTACT = "Rec8_Contact"
MNORMAL = {
"11": FLY_LINE2,
"21": FLY_LINE2,
"22": FLY_LINE3,
"24": FLY_LINE3,
"41": FLY_TRI3,
"81": FLY_TRI3,
"91": FLY_TRI3,
"42": FLY_TRI6,
"82": FLY_TRI6,
"92": FLY_TRI6,
"44": FLY_REC4,
"115": FLY_HEX8,
"116": FLY_HEX20,
"111": FLY_TET4,
"118": FLY_TET10,
}
MCONTACT = {
"115": FLY_REC4_CONTACT,
"116": FLY_REC8_CONTACT,
"112": FLY_TRI3_CONTACT,
}
[docs]
class ParseError(Exception):
pass
[docs]
class UnsupportedElementError(Exception):
pass
[docs]
class EndOfFileError(Exception):
pass
[docs]
class EndOfSectionError(Exception):
pass
[docs]
def scanUnv(file, exclude):
index = {}
groups = {}
nodes = []
contact = set()
t = findSection(file)
while t is not None:
if t == UNV_NODES:
indexNodes(nodes, file)
elif t == UNV_ELEMS:
indexElems(index, file, exclude)
elif t == UNV_GROUPS:
parseGroups(groups, contact, file)
else:
skipSection(file)
t = findSection(file)
return nodes, index, groups, contact
[docs]
def findSection(file):
secType = ""
line = file.readline()
while line and secType == "":
if line.startswith(UNV_DELIM):
secType = file.readline().strip()
else:
line = file.readline()
if secType == "":
return None
return secType
[docs]
def skipSection(file):
line = file.readline()
while not line.startswith(UNV_DELIM):
line = file.readline()
[docs]
def indexNodes(nodes, file):
data = (file.tell(), countUnvNodes(file))
nodes.append(data)
[docs]
def countUnvNodes(file):
num = 7
cnt = 0
data = parse(file, num, 0b1)
while data:
data = parse(file, num, 0b1)
cnt += 1
return cnt
[docs]
def static_vars(**kwargs):
def decorate(func):
for k in kwargs:
setattr(func, k, kwargs[k])
return func
return decorate
[docs]
@static_vars(rem=[])
def parse(f, num, pattern):
cache = filter(parse.rem, 0, pattern)
wCnt = len(parse.rem)
parse.rem = []
line = ""
words = []
while wCnt < num:
line = f.readline()
if line.startswith(UNV_DELIM):
break
words = line.split()
new = filter(words, wCnt, pattern)
cache.extend(new)
wCnt += len(words)
diff = wCnt - num
if diff > 0:
parse.rem = words[len(words) - diff :]
if cache and wCnt < num:
raise EndOfSectionError()
return cache
[docs]
def filter(words, i, pattern):
ret = []
for v in words:
if ((1 << i) & pattern) > 0:
ret.append(v)
i += 1
return ret
[docs]
def indexElems(index, file, exclude):
posPrev = file.tell()
curr = nextType(file)
prev = curr
count = 0
while curr is not None:
count += 1
pos = file.tell()
curr = nextType(file)
if curr != prev:
if prev not in exclude:
data = (posPrev, count)
regIndex(prev, data, index)
posPrev = pos
prev = curr
count = 0
[docs]
def regIndex(t, data, index):
list = index.get(t, [])
list.append(data)
index[t] = list
[docs]
def nextType(file):
data = parse(file, 6, 0b100010)
if data:
t = data[0]
n = int(data[1])
if t in UNV_BEAM:
n += 3
data = parse(file, n, 0b1)
if not data:
raise EndOfSectionError()
return t
return None
[docs]
def parseGroups(groups, contact, file):
data = parse(file, 9, 0b110000000)
while data:
num = int(data[0])
group = data[1]
elems = []
while num > 0:
data = parse(file, 4, 0b0010)
if not data:
raise EndOfSectionError()
(entity,) = data
elems.append(entity)
num -= 1
if group == CONTACT_GRP:
contact |= set(elems)
else:
for e in elems:
groups[e] = group
data = parse(file, 9, 0b110000000)
[docs]
def writeFly(nodes, groups, index, contact, unvFile, flyFile, exclude):
writeHeader(flyFile)
convertNodes(nodes, unvFile, flyFile)
convertElemsContact(index, groups, contact, unvFile, flyFile)
if UNV_index[2].issubset(exclude): # if 2D is excluded
writeFooter(flyFile)
else:
writeFooter2(flyFile)
[docs]
def convertNodes(nodes, unvFile, flyFile):
sum = 0
for pos, num in nodes:
sum += num
flyFile.write("3D-nodes %d\n" % sum)
for pos, num in nodes:
unvFile.seek(pos)
while num > 0:
nId, x, y, z = parseNode(unvFile)
flyFile.write(
nId + " " + nId + " 0 " + str(x) + " " + str(y) + " " + str(z) + "\n"
)
num -= 1
[docs]
def parseNode(file):
return parse(file, 7, 0b1110001)
[docs]
def writeBuffer(f, b, t, m=None, i=1):
if m is not None:
t = m[t]
f.write("%s %d\n" % (t, len(b)))
for ll in b:
f.write(str(i) + " " + ll)
i += 1
return i
[docs]
def addTo(m, k, d):
ls = m.get(k, [])
ls.append(d)
m[k] = ls
[docs]
def parseElem(t, file):
eId, t, nStr = parse(file, 6, 0b100011)
n = int(nStr)
p = ~0b0
if t in UNV_BEAM:
n += 3
p = ~0b111
data = parse(file, n, p)
return eId, data
[docs]
def get_exclude_set(exclude_list):
return functools.reduce(
lambda x, y: x.union(y),
[UNV_index[i] for i in exclude_list],
set(),
)
[docs]
def convert(
unv_path: str | pathlib.Path,
fly_path: str | pathlib.Path,
exclude_list: list[int] = [1, 2],
) -> None:
"""Convert mesh file from `unv` to `fly`.
Args:
unv_path: Input `unv` file path.
fly_path: Output `fly` file path.
exclude_list: List of dimensions to be excluded. Defaults to [1,2], so it will
exclude 1D and 2D elements.
"""
infile = open(unv_path)
pathlib.Path(fly_path).parent.mkdir(exist_ok=True, parents=True)
outfile = open(fly_path, "w")
exclude_set = get_exclude_set(exclude_list)
nodes, index, groups, contact = scanUnv(infile, exclude_set)
writeFly(nodes, groups, index, contact, infile, outfile, exclude_set)
infile.close()
outfile.close()