"""
Part of comet/pyhed/IO
"""
# COMET - COupled Magnetic resonance Electrical resistivity Tomography
# Copyright (C) 2019 Nico Skibbe
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import numpy as np
import os
from pathlib import Path
[docs]class ArgsError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
[docs]def cutExtension(path):
filename = os.path.split(path)[1].split('.')[0]
return os.path.join(os.path.dirname(path), filename)
[docs]class TetgenNotFoundError(Exception):
""" Special Exception to catch in a try except. """
pass
[docs]def searchforTetgen(returnPathfile=False):
""" Try to find a valid tetgen installation for meshing purposes.
Returns:
--------
path: string
Path to tetgen installation or path to pathfile of pyhed itself.
"""
import comet.pyhed as ph
import sys
import subprocess as sp
if sys.version_info[:2] < (3, 3):
error = IOError
else:
error = FileNotFoundError
pathfile = os.path.join(os.path.dirname(ph.__file__), '_PATH_.txt')
try:
p = sp.Popen(['tetgen'], stdout=sp.PIPE)
_, err = p.communicate()
PATH = None
except FileNotFoundError:
try:
with open(pathfile, 'r') as pathf:
PATH = pathf.readline()
while PATH.lstrip().startswith('#'):
PATH = pathf.readline()
try:
try:
PATH = PATH.split('TETGENPATH: ')[1].rstrip()
except TypeError:
PATH = PATH.decode().split('TETGENPATH: ')[1].rstrip()
except IndexError:
PATH = ''
if PATH in ['Enter/path/here/', '']:
raise error
except error:
with open(pathfile, 'w') as pathf:
try:
p = sp.Popen(['which', 'tetgen'], stdout=sp.PIPE)
out, err = p.communicate()
# print(out, err)
if len(out) > 0:
try:
PATH = Path(out.split('tetgen')[0])
except TypeError:
PATH = Path(out.decode().split('tetgen')[0])
if not PATH.exists():
# windows root not detected corretly
# there is a special case that can be intercepted
import platform
if platform.system() == 'Windows':
if PATH.drive == '':
parts = PATH.parts
if parts[0] == '\\':
PATH = Path('{}:/'.format(parts[1]),
*parts[2:])
if PATH.exists():
pathf.writelines('TETGENPATH: {}'
.format(PATH.as_posix()))
else:
raise error
except error:
pathf.writelines('TETGENPATH: Enter/path/here/')
raise Exception(
'Tetgen not found. Please add or link a '
'executabe tetgen instance to your PATH or alter file'
'in "{}"'.format(pathfile))
if returnPathfile is True:
return pathfile
else:
if PATH is None:
return None
else:
return Path(PATH).as_posix()
[docs]def getItem(archive, key, default=None):
"""
Get item for *key* from numpy *archive* via try except with given *default*
value for None.
"""
try:
item = archive[key].item()
except ValueError:
item = archive[key]
except KeyError:
item = default
return item
[docs]def checkDirectory(savename, filename=False, verbose=False):
""" Checks for directory and creates if not. """
from comet.pyhed import log
if '.' in savename and filename is False and not savename.startswith('.'):
filename = True
if filename is True:
dirname = os.path.dirname(savename)
if dirname == '':
if '.' in savename:
directory = '.'
else:
return
else:
directory = os.path.abspath(dirname)
else:
directory = savename
exists = os.path.exists(directory)
log.debug('checkDirectory: "{}" exists? : {}'.format(directory, exists))
log.debug('todo: replace "checkDirectory" with proper pathlib tools')
if not exists:
os.makedirs(directory)
[docs]def checkForFile(name):
""" Checks if file exists and creates a directory if it does not."""
if os.path.exists(name):
return True
else:
checkDirectory(name)
return False
[docs]def delLastLine(opened_file, line_ending='\n'):
""" Efficient way of deleting the last line of a large file. """
opened_file.seek(0, os.SEEK_END) # end of file
pos = opened_file.tell() - 1
while pos > 0 and opened_file.read(1) != line_ending:
pos -= 1
opened_file.seek(pos, os.SEEK_SET)
if pos > 0:
opened_file.seek(pos, os.SEEK_SET)
opened_file.truncate()
return opened_file
[docs]def addVolumeConstraintToPoly(name, regions, float_format='6.3f'):
'''
Append region information in form of volume constraints to a tetgen.poly
file. The given regions has to be of shape (n, 5 or 6), with
n times: [number, x, y, z, regional attribute, volume constraint]
'''
with open(name, "r+") as in_file:
string = '{}'
for i in range(len(regions[0]) - 1): # either 4 or 5 floats possible
string += ' {:%s}' % (float_format)
string += os.linesep
delLastLine(in_file)
in_file.seek(0, os.SEEK_END)
in_file.write(os.linesep)
lines = '# volume constraints{}'.format(os.linesep)
lines += '{:d}{}'.format(len(regions), os.linesep)
for region in regions:
lines += string.format(*region)
in_file.write(lines)
[docs]def createCustEMDirectories(m_dir='.', r_dir='.'):
""" Creates the used custEM directories based on *m_dir* and *r_dir*. """
m_path = Path(m_dir)
r_path = Path(r_dir)
m_path.joinpath('_h5').mkdir(exist_ok=True, parents=True)
m_path.joinpath('_mesh').mkdir(exist_ok=True)
m_path.joinpath('para').mkdir(exist_ok=True)
r_path.joinpath('primary_fields/custom').mkdir(exist_ok=True, parents=True)
r_path.joinpath('E_s').mkdir(exist_ok=True)
r_path.joinpath('H_s').mkdir(exist_ok=True)
#def exportTetgenPolyFile(filename, poly, float_format='.12e'):
# '''
# Writes a given piecewise linear complex (mesh/poly ) into a Ascii file in
# tetgen's .poly format.
#
# Parameters
# ----------
#
# filename: string
# Name in which the result will be written. The recommended file
# ending is '.poly'.
#
# poly: pg.Mesh
# Piecewise linear complex as pygimli mesh to be exported.
#
# float_format: format string ('.12e')
# Format that will be used to write float values in the Ascii file.
# Default is the exponential float form with a precision of 12 digits.
#
# '''
# filename = filename.rstrip('.poly') + '.poly'
# polytxt = ''
# sep = '\t' # standard tab seperated file
# assert poly.dim() == 3, 'Exit, only for 3D meshes.'
# boundary_marker = 1
# attribute_count = 0
#
# # Part 1/4: node list
# # intro line
# # <nodecount> <dimension (3)> <# of attributes> <boundary markers (0 or 1)>
# polytxt += '{0}{5}{1}{5}{2}{5}{3}{4}'.format(poly.nodeCount(), 3,
# attribute_count,
# boundary_marker,
# os.linesep, sep)
# # loop over positions, attributes and marker(node)
# # <point idx> <x> <y> <z> [attributes] [boundary marker]
# point_str = '{:d}' # index of the point
# for i in range(3):
# # coords as float with given precision
# point_str += sep + '{:%s}' % (float_format)
# point_str += sep + '{:d}' + os.linesep # node marker
# for j, node in enumerate(poly.nodes()):
# fill = [node.id()]
# fill.extend([pos for pos in node.pos()])
# fill.append(node.marker())
# polytxt += point_str.format(*fill)
#
# # Part 2/4: boundary list
# # intro line
# # <# of facets> <boundary markers (0 or 1)>
# polytxt += '{0:d}{2}1{1}'.format(poly.boundaryCount(), os.linesep, sep)
# # loop over facets, each facet can contain an arbitrary number of holes
# # and polygons, in our case, there is always one polygon per facet.
# for k, bound in enumerate(poly.boundaries()):
# # one line per facet
# # <# of polygons> [# of holes] [boundary marker]
# npolys = 1
# polytxt += '1{2}0{2}{0:d}{1}'.format(bound.marker(), os.linesep, sep)
# # inner loop over polygons
# # <# of corners> <corner 1> <corner 2> ... <corner #>
# for l in range(npolys):
# poly_str = '{:d}'.format(bound.nodeCount())
# for ind in bound.ids():
# poly_str += sep + '{:d}'.format(ind)
# polytxt += '{0}{1}'.format(poly_str, os.linesep)
# # inner loop over holes
# # not necessary yet ?! why is there an extra hole section?
# # because this is for 2D holes in facets only
#
# # part 3/4: hole list
# # intro line
# # <# of holes>
# holes = poly.holeMarker()
# polytxt += '{:d}{}'.format(len(holes), os.linesep)
# # loop over hole markers
# # <hole #> <x> <y> <z>
# hole_str = '{:d}'
# for m in range(3):
# hole_str += sep + '{:%s}' % float_format
# hole_str += os.linesep
# for n, hole in enumerate(holes):
# polytxt += hole_str.format(n, *hole)
#
# # part 4/4: region attributes and volume constraints (optional)
# # intro line
# # <# of regions>
# regions = poly.regionMarker()
# polytxt += '{:d}{}'.format(len(regions), os.linesep)
# # loop over region markers
# # <region #> <x> <y> <z> <region number> <region attribute>
# region_str = '{:d}'
# for o in range(3):
# region_str += sep + '{:%s}' % (float_format)
# region_str += sep + '{:d}%s{:%s}' % (sep, float_format) + os.linesep
# for p, region in enumerate(regions):
# polytxt += region_str.format(p, region.x(), region.y(), region.z(),
# region.marker(),
# region.area())
#
# # writing file
# with open(filename, 'w') as out:
# out.write(polytxt)
# THE END