Commit 8b71fffa authored by Daniel Böckenhoff (Laptop)'s avatar Daniel Böckenhoff (Laptop)
Browse files

triangles neat, mesh3D messy

parent 64f4270c
......@@ -7,17 +7,17 @@ Mail: daniel.boeckenhoff@ipp.mpg.de
core of tfields library
contains numpy ndarray derived bases of the tfields package
"""
import tfields.bases
import numpy as np
import warnings
import os
import pathlib
from six import string_types
from contextlib import contextmanager
from collections import Counter
import numpy as np
import sympy
import scipy as sp
import scipy.spatial # NOQA: F401
import os
from six import string_types
import pathlib
import warnings
import tfields.bases
np.seterr(all='warn', over='raise')
......@@ -356,19 +356,23 @@ class Tensors(AbstractNdarray):
__slot_setters__ = [tfields.bases.get_coord_system_name]
def __new__(cls, tensors, **kwargs):
dtype = kwargs.pop('dtype', np.float64)
dtype = kwargs.pop('dtype', None)
order = kwargs.pop('order', None)
dim = kwargs.pop('dim', None)
''' copy constructor extracts the kwargs from tensors'''
if issubclass(type(tensors), Tensors):
dtype = tensors.dtype
if dim is not None:
dim = tensors.dim
coordSys = kwargs.pop('coordSys', tensors.coordSys)
tensors = tensors.copy()
tensors.transform(coordSys)
kwargs['coordSys'] = coordSys
if dtype is None:
dtype = tensors.dtype
else:
if dtype is None:
dtype = np.float64
''' demand iterable structure '''
try:
......@@ -443,7 +447,7 @@ class Tensors(AbstractNdarray):
Merge also shifts the maps to still refer to the same tensors
>>> tm_a = tfields.TensorMaps(merge, maps=[[[0, 1, 2]]])
>>> tm_b = tm_a.copy()
>>> tm_a.coordSys
>>> assert tm_a.coordSys == 'cylinder'
>>> tm_merge = tfields.TensorMaps.merged(tm_a, tm_b)
>>> assert tm_merge.coordSys == 'cylinder'
>>> assert tm_merge.maps[0].equal([[0, 1, 2],
......@@ -853,11 +857,14 @@ class Tensors(AbstractNdarray):
>>> import numpy
>>> import sympy
>>> x, y, z = sympy.symbols('x y z')
>>> p = tfields.Points3D([[1., 2., 3.], [4., 5., 6.], [1, 2, -6],
... [-5, -5, -5], [1,0,-1], [0,1,-1]])
>>> p = tfields.Tensors([[1., 2., 3.], [4., 5., 6.], [1, 2, -6],
... [-5, -5, -5], [1,0,-1], [0,1,-1]])
>>> np.array_equal(p.evalf(x > 0),
... [True, True, True, False, True, False])
True
>>> np.array_equal(p.evalf(x >= 0),
... [True, True, True, False, True, True])
True
And combination
>>> np.array_equal(p.evalf((x > 0) & (y < 3)),
......@@ -1181,14 +1188,18 @@ class TensorFields(Tensors):
"""
item = super(TensorFields, self).__getitem__(index)
if issubclass(type(item), TensorFields):
if isinstance(index, slice):
item.fields = [field.__getitem__(index) for field in item.fields]
elif isinstance(index, tuple):
item.fields = [field.__getitem__(index[0]) for field in item.fields]
else:
if item.fields:
try:
if issubclass(type(item), TensorFields):
if isinstance(index, tuple):
index = index[0]
if isinstance(index, slice):
item.fields = [field.__getitem__(index) for field in item.fields]
else:
if item.fields:
item.fields = [field.__getitem__(index) for field in item.fields]
except IndexError as err:
warnings.warn("Index error occured for field.__getitem__. Error "
"message: {err}".format(**locals()))
return item
......@@ -1527,8 +1538,8 @@ class TensorMaps(TensorFields):
if __name__ == '__main__': # pragma: no cover
import doctest
# doctest.testmod()
doctest.run_docstring_examples(TensorMaps, globals())
doctest.testmod()
# doctest.run_docstring_examples(TensorMaps, globals())
# doctest.run_docstring_examples(TensorFields, globals())
# doctest.run_docstring_examples(AbstractNdarray.copy, globals())
# doctest.run_docstring_examples(TensorMaps.equal, globals())
......@@ -42,9 +42,13 @@ def evalf(array, cutExpression=None, coords=None):
If array of other shape than (?, 3) is given, the coords need to be specified
>>> a0, a1 = sympy.symbols('a0 a1')
>>> assert np.array_equal(tfields.evalf([[0., 1.], [-1, 3]], a1 > 2,
... coords=[a0, a1]),
... coords=[a0, a1]),
... np.array([False, True], dtype=bool))
>= is taken care of
>>> assert np.array_equal(tfields.evalf(a, x >= 0),
... np.array([ True, True, True, False, True, True]))
"""
if isinstance(array, list):
array = np.array(array)
......@@ -57,7 +61,6 @@ def evalf(array, cutExpression=None, coords=None):
coords = sympy.symbols('x y z')
else:
raise ValueError("coords are None and shape is not (?, 3)")
elif len(coords) != array.shape[1]:
raise ValueError("Length of coords is not {0} but {1}".format(array.shape[1], len(coords)))
......@@ -65,7 +68,7 @@ def evalf(array, cutExpression=None, coords=None):
cutExpression,
modules={'&': np.logical_and, '|': np.logical_or})
mask = np.array([preMask(*x) for x in array], dtype=bool)
mask = np.array([preMask(*vals) for vals in array], dtype=bool)
return mask
......
......@@ -149,134 +149,6 @@ class Mesh3D(tfields.TensorMaps):
def faceScalars(self, scalars):
self.maps[0].fields = scalars_to_fields(scalars)
@classmethod
def _updateSlotKwargs(cls, kwargs, skipCache=True):
faces = kwargs.pop('faces', None)
faceScalars = kwargs.pop('faceScalars', None)
# Add faces
if faces is None or len(faces) == 0:
faces = np.empty((0, 3), dtype=int)
faces = np.array(faces, dtype=int)
kwargs['faces'] = faces
# Add faceScalars
if faceScalars is None or len(faceScalars) == 0:
faceScalars = np.empty((len(faces), 0), dtype=float)
if not len(faceScalars) == len(faces):
raise ValueError("Length of faceScalars has to be the same as faces lenght "
"({0}) but is {1}.".format(len(faces), len(faceScalars)))
# demand 2d structure of faceScalars
faceScalars = np.array(faceScalars, dtype=float)
if len(faceScalars.shape) == 1:
faceScalars = faceScalars.reshape(faces.shape[0], -1)
kwargs['faceScalars'] = faceScalars
super(Mesh3D, cls)._updateSlotKwargs(kwargs, skipCache=skipCache)
@classmethod
def createFromObjFile(cls, filePath, *groupNames):
"""
Factory method
Given a filePath to a obj/wavefront file, construct the object
"""
ioCls = ioTools.ObjFile
with ioCls(filePath, 'r') as f:
f.process()
vertices, faces = f.getVerticesFaces(*groupNames, firstFace=0)
log = logger.new()
if len(vertices) == 0:
return cls([])
faceLenghts = [len(face) for face in faces]
for i in reversed(range(len(faceLenghts))):
length = faceLenghts[i]
if length == 3:
continue
if length == 4:
log.warning("Given a Rectangle. I will split it but "
"sometimes the order is different.")
faces.insert(i + 1, faces[i][2:] + faces[i][:1])
faces[i] = faces[i][:3]
else:
raise NotImplementedError()
mesh = cls(vertices, faces=faces)
if groupNames:
mesh = mesh.clean()
return mesh
@classmethod
def createFromInpFile(cls, filePath, **kwargs):
"""
Factory method
Given a filePath to a inp file, construct the object
"""
import transcoding as tc
transcoding = tc.getTranscoding('inp')
content = transcoding.read(filePath)
part = content['parts'][0]
vertices = np.array([part['x'], part['y'], part['z']]).T / 1000
indices = np.array(part['nodeIndex']) - 1
if not list(indices) == range(len(indices)):
raise ValueError("node index skipped")
faces = np.array([part['nodeIndex{i}'.format(i=i)] for i in range(3)]).T - 1
return cls(vertices, faces=faces)
@classmethod
def createMeshGrid(cls, *baseVectors, **kwargs):
if not len(baseVectors) == 3:
raise AttributeError("3 baseVectors vectors required")
indices = [0, -1]
coords = range(3)
baseLengthsAbove1 = [len(b) > 1 for b in baseVectors]
# if one plane is given: rearrange indices and coords
if not all(baseLengthsAbove1):
indices = [0]
for i, b in enumerate(baseLengthsAbove1):
if not b:
coords = [i]
break
baseVectors = list(baseVectors)
mParts = []
for ind in indices:
for coord in coords:
basePart = baseVectors[:]
basePart[coord] = np.array([baseVectors[coord][ind]],
dtype=float)
mParts.append(cls.createMeshPlane(*basePart))
inst = cls.__new__(cls, mParts, **kwargs)
return inst
@classmethod
def createMeshPlane(cls, *baseVectors, **kwargs):
points = tfields.Points3D.createMeshGrid(*baseVectors)
fixCoord = None
for coord in range(3):
if len(baseVectors[coord]) > 1:
continue
if len(baseVectors[coord]) == 0:
continue
fixCoord = coord
variableCoords = list(range(3))
variableCoords.pop(variableCoords.index(fixCoord))
faces = []
base0, base1 = baseVectors[variableCoords[0]], baseVectors[variableCoords[1]]
for i1 in range(len(base1) - 1):
for i0 in range(len(base0) - 1):
pointIdxTopLeft = len(base1) * (i0 + 0) + (i1 + 0)
pointIdxTopRight = len(base1) * (i0 + 0) + (i1 + 1)
pointIdxBotLeft = len(base1) * (i0 + 1) + (i1 + 0)
pointIdxBotRight = len(base1) * (i0 + 1) + (i1 + 1)
faces.append([pointIdxTopLeft, pointIdxTopRight, pointIdxBotLeft])
faces.append([pointIdxTopRight, pointIdxBotLeft, pointIdxBotRight])
inst = cls.__new__(cls, points, faces=faces, **kwargs)
return inst
@decoTools.cached_property()
def triangles(self):
"""
......@@ -294,180 +166,11 @@ class Mesh3D(tfields.TensorMaps):
def planes(self):
if self.faces.size == 0:
return tfields.Planes3D([])
return tfields.Planes3D(self.getCentroids(), self.getNormVectors())
return tfields.Planes3D(self.getCentroids(), self.triangles.norms())
def saveTxt(self, filePath):
if self.coordSys != self.CARTESIAN:
cpy = self.copy()
cpy.coordinateTransform(self.CARTESIAN)
else:
cpy = self
with ioTools.TextFile(filePath, 'w') as f:
matrix = []
for i, face in enumerate(self.faces):
matrix.append(self.faceScalars[i, :])
matrix.extend(self[face])
f.writeMatrix(matrix, seperator=' ', lineBreak='\n')
@classmethod
def createFromTxtFile(cls, filePath):
return tfields.Triangles3D.createFromTxtFile(filePath).getMesh3D()
# def __getattr__(self, name):
# """
# getter methods are forwarded to self.triangles
# Examples:
# >>> m = tfields.Mesh3D([]);
# >>> m.getAreas
# <bound method Triangles3D.getAreas of Triangles3D([], shape=(0, 3), dtype=float64)>
# """
# if name.startswith('get'):
# if not hasattr(tfields.Triangles3D, name) or name == 'getMask':
# raise AttributeError("Could not forward attribute {0}".format(name))
# else:
# return getattr(self.triangles, name)
# else:
# raise AttributeError("No attribute with name {0}".format(name))
def getNFaces(self):
def nfaces(self):
return self.faces.shape[0]
def getMean(self, *args, **kwargs):
"""
Forward this manually since getMean is derived already.
"""
return self.triangles.getMean(*args, **kwargs)
def getStd(self, *args, **kwargs):
"""
Forward this manually since getStd is derived already.
"""
return self.triangles.getStd(*args, **kwargs)
def getScalars(self):
return self.faceScalars
def getScalarArrays(self):
return self.faceScalars.T
def getScalarDepth(self):
return self.faceScalars.shape[1]
def setScalarArray(self, scalarIndex, scalarArray):
"""
>>> m = tfields.Mesh3D([[1,2,3], [3,3,3],
... [0,0,0], [5,6,7]],
... [[0, 1, 2], [1, 2, 3]],
... faceScalars=[[1,2,3,4,5],
... [6,7,8,9,0]])
>>> m.setScalarArray(1, [42, 84])
>>> m.faceScalars
array([[ 1., 42., 3., 4., 5.],
[ 6., 84., 8., 9., 0.]])
"""
if scalarIndex == self.getScalarDepth():
self.appendScalars(scalarArray)
else:
self.faceScalars[:, scalarIndex] = scalarArray
# try:
# self.faceScalars[:, scalarIndex] = scalarArray
# except:
# tfields.saveNested("~/tmp/bug1e-6.nest.npz", (self, scalarArray,
# scalarIndex))
# raise
def removeScalars(self, scalarIndex=None):
if scalarIndex is not None:
raise NotImplementedError()
self.faceScalars = np.empty(self.faceScalars.shape[0],
dtype=self.faceScalars.dtype)
def appendScalars(self, scalars):
"""
Similar to list.append but in axis 1
"""
self.faceScalars = np.concatenate([self.faceScalars, np.array([scalars]).T], 1)
def stackScalars(self, *stack, **kwargs):
"""
add all faceScalars of stack meshes to self.faceScalars.
Args:
*stack (Mesh3D): input meshes are required to have the
same or simiar faces. This is not tested for time reasons
though.
**kwargs:
mapping (str):
'centroid': Use centroids for check
which face belongs to which.
'order': Assume all faces are in the same order.
Examples:
>>> m = tfields.Mesh3D([[1,0,0],[0,1,0],[0,0,1], [0,0,0]],
... [[0,1,2], [2,3,0], [2,3,1]],
... faceScalars=[1,2,3])
>>> c = m.copy()
If you exactly know, the two meshes are the same:
>>> m.stackScalars(c, mapping='order')
>>> all(m.getScalarArrays()[0, :] == [2.,4.,6.])
True
Using the default compares centroids
>>> m = c.copy()
>>> m.stackScalars(c, mapping='centroid')
>>> all(m.getScalarArrays()[0, :] == [2.,4.,6.])
True
"""
mapping = kwargs.pop('mapping', 'centroid')
for stackObj in stack:
"""
faceMap is a list of tuples: first meshFaceIndex second
selfFaceIndex
"""
if mapping == 'order':
if not isinstance(stackObj, Mesh3D):
raise NotImplementedError()
faceRange = range(stackObj.getNFaces())
faceMap = [(i, i) for i in faceRange]
objScalars = stackObj.faceScalars
elif mapping == 'centroid':
if isinstance(stackObj, Mesh3D):
faceRange = range(stackObj.getNFaces())
centroids = stackObj.getCentroids()
objScalars = stackObj.faceScalars
elif isinstance(stackObj, tfields.ScalarField3D):
faceRange = range(stackObj.getNPoints())
centroids = stackObj
if mapping != 'centroid':
raise NotImplementedError()
objScalars = stackObj.scalars
else:
raise NotImplementedError()
closestCentroidIndices = \
centroids.closestPoints(self.getCentroids())
faceMap = [(i, j) for j, i in zip(faceRange, closestCentroidIndices)]
else:
raise NotImplementedError()
faceMap = np.array(faceMap)
"""
Rearrange scalars according to faceMap
This is the part that takes longest
"""
scalars = np.full(self.faceScalars.shape,
0.,
dtype=objScalars.dtype)
for i in set(faceMap[:, 0]):
scalars[i] = objScalars[faceMap[faceMap[:, 0] == i,
1]].sum(axis=0)
"""
Add all scalars
"""
self.faceScalars = self.faceScalars + scalars
def toOneSegment(self, mirrorZ=True):
"""
Map the points to the first segment and mirror to positive z
......@@ -499,15 +202,15 @@ class Mesh3D(tfields.TensorMaps):
dropCut = (-2 * np.pi / 10 < y) & (y < 2 * np.pi / 10)
if mirrorZ:
dropCut = dropCut & (z > 0)
dropCutMask = self.getMask(dropCut)
dropCutMask = self.evalf(dropCut)
faceKeepMask = self.getFaceMask(dropCutMask)
excludedMesh = self.copy()
self.removeFaces(~faceKeepMask)
excludedMesh.removeFaces(faceKeepMask)
# remove 0 faces to be faster
from sympy.abc import s
zeroMask = tfields.getMask(excludedMesh.faceScalars,
cutExpression=(s == 0),
zeroMask = tfields.evalf(excludedMesh.faceScalars,
expression=(s == 0),
coords=[s] * excludedMesh.getScalarDepth())
excludedMesh.removeFaces(zeroMask)
......@@ -519,40 +222,20 @@ class Mesh3D(tfields.TensorMaps):
self.stackScalars(centroidSF)
def pickScalars(self, *scalarIndices):
"""
Reduces the faceScalars to only the indices given.
Examples:
>>> m = tfields.Mesh3D([[1,2,3], [3,3,3], [0,0,0], [5,6,7]],
... [[0, 1, 2], [1, 2, 3]],
... faceScalars=[[1,2,3,4,5], [6,7,8,9,0]])
>>> m.pickScalars(1, 3, 4)
>>> m.faceScalars
array([[ 2., 4., 5.],
[ 7., 9., 0.]])
"""
self.faceScalars = self.faceScalars[:, list(scalarIndices)]
def pointsInMesh(self, points, delta, method='baryc', assignMultiple=False):
def in_faces(self, points, delta, assign_multiple=False):
"""
Check whether points lie within triangles with Barycentric Technique
"""
masks = self.triangles.pointsInTriangles(points, delta, method='baryc',
assignMultiple=assignMultiple)
masks = self.triangles.in_triangles(points, delta,
assign_multiple=assign_multiple)
return masks
def convertNaN(self, value=0.):
super(Mesh3D, self).convertNaN(value)
nanIndicesScalars = np.isnan(self.faceScalars)
self.faceScalars[nanIndicesScalars] = value
def cutScalars(self, cutExpression, coords=None,
def cutScalars(self, expression, coords=None,
replaceValue=np.nan, scalarIndex=None, inplace=False):
"""
Set a threshold to the scalars.
Args:
cutExpression (sympy cut expression or list of those):
expression (sympy cut expression or list of those):
threshold(sympy cut expression): cut scalars globaly
threshold(list of sympy cut expressions): set on threshold for every scalar array
Examples:
......@@ -584,37 +267,37 @@ class Mesh3D(tfields.TensorMaps):
else:
inst = self.copy()
if isinstance(cutExpression, list):
if isinstance(expression, list):
if scalarIndex is not None:
raise ValueError("scalarIndex must be None, "
"if cutExpression is list of cutExpressions")
if not len(cutExpression) == inst.getScalarDepth():
raise ValueError("lenght of cutExpression must meet scalar depth")
for si, ce in enumerate(cutExpression):
"if expression is list of expressions")
if not len(expression) == inst.getScalarDepth():
raise ValueError("lenght of expression must meet scalar depth")
for si, ce in enumerate(expression):
inst.cutScalars(ce, coords=coords,
replaceValue=replaceValue,
scalarIndex=si, inplace=True)
else:
if coords is None:
freeSymbols = cutExpression.free_symbols
freeSymbols = expression.free_symbols
if len(freeSymbols) > 1:
raise ValueError('coords must be given if multiple variables are given')
elif len(freeSymbols) == 0:
raise NotImplementedError("Expressiongs like {cutExpression} "
raise NotImplementedError("Expressiongs like {expression} "
"are not understood for coords".format(**locals()))
coords = list(freeSymbols) * inst.getScalarDepth()
scalarArrays = inst.getScalars()
if scalarIndex is not None:
scalarArrays = scalarArrays[:, scalarIndex:scalarIndex + 1]
maskBelow = tfields.getMask(scalarArrays,
cutExpression=cutExpression,
maskBelow = tfields.evalf(scalarArrays,
expression=expression,
coords=[coords[scalarIndex]])
scalarArrays[maskBelow] = replaceValue
inst.faceScalars[:, scalarIndex:scalarIndex + 1] = scalarArrays
else:
maskBelow = tfields.getMask(scalarArrays,
cutExpression=cutExpression,
maskBelow = tfields.evalf(scalarArrays,
expression=expression,
coords=coords)
scalarArrays[maskBelow] = replaceValue
inst.faceScalars = scalarArrays
......@@ -628,7 +311,7 @@ class Mesh3D(tfields.TensorMaps):
... [[0, 1, 2], [1, 2, 3]],
... faceScalars=[[1,2,3,4,5], [6,7,8,9,0]])
>>> from sympy.abc import x,y,z
>>> vertexMask = m.getMask(z < 6)
>>> vertexMask = m.evalf(z < 6)
>>> faceMask = m.getFaceMask(vertexMask)
>>> faceMask
array([ True, False], dtype=bool)
......@@ -637,7 +320,7 @@ class Mesh3D(tfields.TensorMaps):
mask of faces with all vertices in mask
"""
faceDeleteMask = np.full((self.faces.shape[0]), False, dtype=bool)
indices = np.array(range(self.getNPoints()))
indices = np.array(range(len(self)))
deleteIndices = set(indices[~mask]) # set speeds up everything
for i, face in enumerate(self.faces):
for index in face:
......@@ -682,7 +365,7 @@ class Mesh3D(tfields.TensorMaps):
moveUpCounter = np.zeros(self.faces.shape, dtype=int)
# correct faces:
deleteIndices = np.arange(self.getNPoints())[vertexDeleteMask]
deleteIndices = np.arange(len(self))[vertexDeleteMask]
for p in deleteIndices:
moveUpCounter[self.faces > p] -= 1
......@@ -735,7 +418,7 @@ class Mesh3D(tfields.TensorMaps):
# redirect faces
log.verbose("Run trough all faces to let it point to the"
"original")
for f in range(self.getNFaces()):
for f in range(self.nfaces()):
if i in self.faces[f]:
index = tfields.index(self.faces[f], i)
inst.faces[f][index] = dupi
......@@ -756,7 +439,7 @@ class Mesh3D(tfields.TensorMaps):
"""
# built instance that only contains the vaild points
faceKeepMask = self.getFaceMask(mask)
scalarMap = np.arange(self.getNFaces())[faceKeepMask]
scalarMap = np.arange(self.nfaces())[faceKeepMask]
return scalarMap