Source code for spam.mesh.unstructured

# Library of SPAM functions for dealing with unstructured 3D meshes made of tetrahedra
# Copyright (C) 2020 SPAM Contributors
#
# 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 <http://www.gnu.org/licenses/>.
"""
This module offers a set of tools for unstructured 3D meshes made of tetrahedra.

>>> # import module
>>> import spam.mesh
>>> spam.mesh.createCuboid()
"""

import multiprocessing

try:
    multiprocessing.set_start_method("fork")
except RuntimeError:
    pass

import gmsh
import numpy
import progressbar
import scipy
import spam.mesh

# Global number of processes
nProcessesDefault = multiprocessing.cpu_count()


[docs] def gmshToSpam(elementsByType, nodesByType): """Helper function Converts gmsh mesh data to SPAM format by: 1. reordering by z y x 2. keeping only tetrahedra Parameters ---------- elementsByType: array Should be the output of `gmsh.model.mesh.getElementsByType(4)` nodesByType: array Should be the output of `gmsh.model.mesh.getNodesByType(4)` Returns ------- points: 2D numpy array The coordinates of the mesh nodes (zyx) Each line is [zPos, yPos, xPos] connectivity: 2D numpy array The connectivity matrix of the tetrahedra elements Each line is [node1, node2, node3, node4] """ # Get connectivity and node coordinates element_tags, node_tags_element = elementsByType node_tags, coord, _ = nodesByType # NOTE: gmsh returns coordinates in xyz. This means that: # 1. the coordinates array must be switched back to zyx # 2. the connectivity array must change so that the nodes of each tetrahedron are numbered # in such a way that the Jacobian is positive. Which means a permutation of the node numbering. # 3. in some cases the connectivity matrix has some holes in the node numerotations (some nodes are not used) # it needs to be rebuild for the projection # get number of nodes and elements n_elements = element_tags.shape[0] nodes_set = set(node_tags) n_nodes = len(nodes_set) # get new node numbering (without holes) new_node_numbering = {} for i, n in enumerate(nodes_set): new_node_numbering[n] = i # reshape the connectivity matrix from flatten gmsh output connectivity = node_tags.reshape((n_elements, 4)) # create nodes matrix nodes = numpy.zeros((n_nodes, 3)) for i, (nodes_4x1, coord_4x3) in enumerate(zip(connectivity, coord.reshape((n_elements, 4, 3)))): # change connectivity with new orderning connectivity[i] = [new_node_numbering[n] for n in nodes_4x1] # fill node vector with new connectivity numbering and switch x&z for j, n in enumerate(connectivity[i]): nodes[n] = coord_4x3[j][::-1] # rearange connectivity _ = connectivity.copy() connectivity[:, 1] = _[:, 3] connectivity[:, 3] = _[:, 1] i = 0 for e in list(set(connectivity.ravel())): if e != i: print("unused node {e}") i += 1 return nodes, connectivity
[docs] def getMeshCharacteristicLength(points, connectivity): """ Computes the average distance between two nodes of the edges of each elements. Parameters ---------- points: Nx3 array List of coordinates of the mesh nodes. connectivity: Mx4 array Connectivity matrix of the mesh. Returns ------- float: the characteristic length """ def _computeDist(n1, n2, points): d = [(x1 - x2) ** 2 for x1, x2 in zip(points[n1], points[n2])] return sum(d) ** 0.5 lc = 0.0 for n1, n2, n3, n4 in connectivity: lc += sum( [_computeDist(n1, n2, points), _computeDist(n1, n3, points), _computeDist(n1, n4, points), _computeDist(n2, n3, points), _computeDist(n2, n4, points), _computeDist(n3, n4, points)] ) / (6.0 * len(connectivity)) return lc
[docs] def createCuboid( lengths, lc, origin=[0.0, 0.0, 0.0], periodicity=False, verbosity=1, gmshFile=None, vtkFile=None, binary=False, skipOutput=False, ): """ Creates an unstructured mesh of tetrahedra inside a cuboid. Parameters ---------- lengths: 1D array The lengths of the cuboid in the three directions The axis order is zyx origin: 1D array The origin of the cuboid (zyx) lc: float characteristic length of the elements of the mesh (`i.e.`, the average distance between two nodes) periodicity: bool, optional if periodicity is True, the generated cube will have a periodicity of mesh on surfaces Default = False gmshFile: string, optional If not None, save the gmsh file with name ``gmshFile`` and suffix ``.msh`` Default = None vtkFile: string, optional If not None, save the vtk file with name ``vtkFile`` and suffix ``.vtk`` Defaut = None binary: bool, optional Save files in binary when possible Default = False skipOutput: bool, optional Returns None to save time (only write the files) Default = False verbosity: int, optional Level of information printed on the terminal and the message console. 0: silent except for fatal errors 1: +errors 2: +warnings 3: +direct 4: +information 5: +status 99: +debug Default = 1 Returns ------- points: 2D numpy array The coordinates of the mesh nodes (zyx) Each line is [zPos, yPos, xPos] connectivity: 2D numpy array The connectivity matrix of the tetrahedra elements Each line is [node1, node2, node3, node4] Example ------- >>> points, connectivity = spam.mesh.createCuboid((1.0,1.5,2.0), 0.5) create a mesh in a cuboid of size 1,1.5,2 with a characteristic length of 0.5 """ # We will switch the input arrays from zyx to xyz before passing them to gmsh lengths = lengths[::-1] origin = origin[::-1] lx, ly, lz = lengths ox, oy, oz = origin # https://gmsh.info/doc/texinfo/gmsh.pdf # initialize mesh gmsh.initialize() gmsh.model.add("SPAM cuboid") # set mesh length options # gmsh.option.setNumber("Mesh.MeshSizeMin", lc) # gmsh.option.setNumber("Mesh.MeshSizeMax", lc) gmsh.option.setNumber("Mesh.Algorithm", 1) # is the delaunay one (it's the gmsh default) gmsh.option.setNumber("Mesh.Optimize", True) gmsh.option.setNumber("Mesh.OptimizeNetgen", True) # gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0) # gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0) # gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0) # set general options gmsh.option.setNumber("General.Verbosity", verbosity) gmsh.option.setNumber("Mesh.Binary", binary) # Create cuboid geometry gmsh.model.occ.addBox(ox, oy, oz, lx, ly, lz) # create cube gmsh.model.occ.synchronize() # set mesh density at all points of the box gmsh.model.mesh.setSize(gmsh.model.getEntities(0), lc) # set mesh density to the 8 vertices gmsh.model.occ.synchronize() if periodicity: def tr(t): phi = [1, 0, 0, t[0], 0, 1, 0, t[1], 0, 0, 1, t[2], 0, 0, 0, 1] phi = numpy.array(phi).astype("<f4") return phi gmsh.model.mesh.setPeriodic(2, [2], [1], tr([lx, 0, 0])) # surface -> dim=2, surface 2 set as surface 1 based on translationX gmsh.model.mesh.setPeriodic(2, [4], [3], tr([0, ly, 0])) # surface -> dim=2, surface 4 set as surface 3 based on translationY gmsh.model.mesh.setPeriodic(2, [6], [5], tr([0, 0, lz])) # surface -> dim=2, surface 6 set as surface 5 based on translationZ gmsh.model.occ.synchronize() # Generate mesh and optimize gmsh.model.mesh.generate(3) gmsh.model.occ.synchronize() # write gmsh/vtk file if gmshFile is not None: gmsh.write(f"{gmshFile}.msh") # can have additional nodes if vtkFile is not None: gmsh.write(f"{vtkFile}.vtk") # finish here if no output if skipOutput: gmsh.finalize() return None # Get connectivity and node coordinates nodes, connectivity = gmshToSpam(gmsh.model.mesh.getElementsByType(4), gmsh.model.mesh.getNodesByElementType(4)) # DEBUG: gmsh GUI # gmsh.fltk.run() # done with gmsh gmsh.finalize() # if vtkFile is not None: # meshio.write_points_cells( # f"{vtkFile}.vtk", # nodes, # {"tetra": connectivity}, # ) # return coordinates and connectivity matrix return nodes, connectivity
[docs] def createCylinder( centre, radius, height, lc, zOrigin=0.0, membrane=0.0, verbosity=0, gmshFile=None, vtkFile=None, binary=False, skipOutput=False, ): """ Creates an unstructured mesh of tetrahedra inside a cylinder. The height of the cylinder is along the z axis. Parameters ---------- centre: 1D array The two coordinates of the centre of the base disk (yx). radius: float The radius of the base disk. height: float The height of the cylinder. lc: float characteristic length of the elements of the mesh (`i.e.`, the average distance between two nodes) zOrigin: float, default = 0.0 Translate the points coordinates by zOrigin in the z direction. membrane: float, default = 0.0 Radius of the membrane (pipe added outside of the cylinder). If membrane < lc the membrane is ignored. gmshFile: string, optional If not None, save the gmsh file with name ``gmshFile`` and suffix ``.msh`` Default = None vtkFile: string, optional If not None, save the vtk file with name ``vtkFile`` and suffix ``.vtk`` Defaut = None binary: bool, optional Save files in binary when possible Default = False skipOutput: bool, optional Returns None to save time (only write the files) Default = False verbosity: int, optional GMSH level of information printed on the terminal and the message console. 0: silent except for fatal errors 1: +errors 2: +warnings 3: +direct 4: +information 5: +status 99: +debug Default = 1 Returns ------- points: 2D numpy array The coordinates of the mesh nodes (zyx) Each line is [zPos, yPos, xPos] connectivity: 2D numpy array The connectivity matrix of the tetrahedra elements Each line is [node1, node2, node3, node4] Example ------- >>> points, connectivity = spam.mesh.createCylinder((0.0,0.0), 0.5, 2.0, 0.5) create a mesh in a cylinder of centre 0,0,0 radius, 0.5 and height 2.0 with a characteristic length of 0.5 """ # unpack cy, cx = centre r = radius # https://gmsh.info/doc/texinfo/gmsh.pdf # initialize mesh gmsh.initialize() gmsh.model.add("SPAM cylinder") # set mesh length options gmsh.option.setNumber("Mesh.MeshSizeMin", lc) gmsh.option.setNumber("Mesh.MeshSizeMax", lc) gmsh.option.setNumber("Mesh.Algorithm", 5) # is the delaunay one gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0) gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0) gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0) # set general options gmsh.option.setNumber("General.Verbosity", verbosity) gmsh.option.setNumber("Mesh.Binary", binary) # Create disk surface p0 = gmsh.model.geo.addPoint(cx, cy, zOrigin, lc, 1) p1 = gmsh.model.geo.addPoint(cx + r, cy, zOrigin, lc, 2) p2 = gmsh.model.geo.addPoint(cx, cy + r, zOrigin, lc, 3) p3 = gmsh.model.geo.addPoint(cx - r, cy, zOrigin, lc, 4) p4 = gmsh.model.geo.addPoint(cx, cy - r, zOrigin, lc, 5) c1 = gmsh.model.geo.addCircleArc(p1, p0, p2) c2 = gmsh.model.geo.addCircleArc(p2, p0, p3) c3 = gmsh.model.geo.addCircleArc(p3, p0, p4) c4 = gmsh.model.geo.addCircleArc(p4, p0, p1) l1 = gmsh.model.geo.addCurveLoop([c1, c2, c3, c4]) s1 = gmsh.model.geo.addPlaneSurface([l1]) # Create membrane surface if membrane > lc: r += membrane p5 = gmsh.model.geo.addPoint(cx + r, cy, zOrigin, lc, 6) p6 = gmsh.model.geo.addPoint(cx, cy + r, zOrigin, lc, 7) p7 = gmsh.model.geo.addPoint(cx - r, cy, zOrigin, lc, 8) p8 = gmsh.model.geo.addPoint(cx, cy - r, zOrigin, lc, 9) c5 = gmsh.model.geo.addCircleArc(p5, p0, p6) c6 = gmsh.model.geo.addCircleArc(p6, p0, p7) c7 = gmsh.model.geo.addCircleArc(p7, p0, p8) c8 = gmsh.model.geo.addCircleArc(p8, p0, p5) l2 = gmsh.model.geo.addCurveLoop([c5, c6, c7, c8]) s2 = gmsh.model.geo.addPlaneSurface([l1, l2]) # elif membrane > : # warnings.warn(f"membrane thickness is smaller than the characteristic length of the mesh ({membrane} < {lc})") gmsh.model.geo.synchronize() # Create volume gmsh.model.geo.extrude([(2, s1)], 0, 0, height) if membrane > lc: gmsh.model.geo.extrude([(2, s2)], 0, 0, height) # Generate mesh and optimize gmsh.model.geo.synchronize() gmsh.model.mesh.generate() gmsh.model.mesh.optimize("Netgen", True) # DEBUG: gmsh GUI # gmsh.fltk.run() # write gmsh/vtk file if gmshFile is not None: gmsh.write(f"{gmshFile}.msh") # can have additional nodes if vtkFile is not None: gmsh.write(f"{vtkFile}.vtk") # finish here if no output if skipOutput: gmsh.finalize() return None # Get connectivity and node coordinates nodes, connectivity = gmshToSpam(gmsh.model.mesh.getElementsByType(4), gmsh.model.mesh.getNodesByElementType(4)) # done with gmsh gmsh.finalize() # if vtkFile is not None: # meshio.write_points_cells( # f"{vtkFile}.vtk", # nodes, # {"tetra": connectivity}, # ) # return coordinates and connectivity matrix return nodes, connectivity
[docs] def triangulate(points, alpha=None, weights=None): """ Takes a series of points and optionally their weights and returns a tetrahedral connectivity matrix. If a completely regular grid is passed, there will be trouble, add some tiny noise. This function uses CGAL's Regular Triangulation in the background (with all weights=1 if they are not passed). Weights are passed to CGAL's Regular Triangulation directly and so should be squared if they are radii of particles. Users can optionally pass an alpha value to the function with the goal of carving flat boundary tetrahedra away from the final mesh. Typical use of the alpha shape could be the removal of flat (almost 0 volume) boundary tetrahedra from concave/convex boundaries. Another example might be the removal of tetrahedra from an internal void that exists within the domain. In all cases, the user can imagine the alpha tool as a spherical "scoop" that will remove any tetrahedra that it is capable of entering. It follows that flat tetrahedra have a very large open face which are easily entered by large alpha "scoops". Thus, the user should imagine using a very large alpha value (try starting with 5*domain size) to avoid letting the alpha "scoop" enter well formed tetrahedra. Consider the following CGAL analogy: The full mesh is an ice cream sundae and the vertices are the chocolate chips. The value of alpha is the squared radius of the icecream scoop (following the mesh coordinate units) that will go in and remove any tetrahedra that it can pass into. Positive alpha is user defined, negative alpha allows CGAL to automatically select a continuous solid (not recommended). For more information visit: https://doc.cgal.org/latest/Alpha_shapes_3/index.html Parameters ---------- points : Nx3 2D numpy array of floats N points to triangulate (Z, Y, X) weights : numpy vector of N floats list of weights associated with points. Default = None (which means all weights = 1). alpha : float size of the alpha shape used for carving the mesh. Zero is no carving. Negative is CGAL-autocarve which gives an overcut mesh. positive is a user-selected size, try 5*domain size. Default = 0 (no carving). Returns ------- connectivity : Mx4 numpy array of unsigned ints delaunay triangulation with each row containing point numbers Note ---- Function contributed by Robert Caulk (Laboratoire 3SR, Grenoble) """ # There are two passes here -- CGAL is all based in templates, and just stores the triangulation # in a smart way. # We want the connectivity matrix, which CGAL doesn't know about, so we use a first pass to get the # number of tetrahedra, so that we can allocate the connectivity matrix in python, and pass it to # the next pass through the code, which fills the connectivity matrix if weights is None: weights = numpy.ones(len(points)) elif weights.shape[0] != points.shape[0]: raise Exception("weights array dim1 != points array dim1") if alpha is None: alpha = numpy.array([0]) else: alpha = numpy.array([alpha]) points = points.astype("<f4") weights = weights.astype("<f4") alpha = alpha.astype("<f4") # get the number of tetrahedra so we can properly size our connectivityMatrix nTet = spam.mesh.meshToolkit.countTetrahedraCGAL(points, weights, alpha) connectivity = numpy.zeros([nTet, 4], dtype="<u4") # setup the return types and argument types spam.mesh.meshToolkit.triangulateCGAL(points, weights, connectivity, alpha) return connectivity
[docs] def projectTetFieldToGrains(points, connectivity, tetField): """ Projects/coarse-grains any field defined on tetrahedra onto grains by volume-averaging over all tetrahedra for which a given grain is a node. This can be useful for smoothing out a noisy strain field and will not affect the overall agreement between the average of strains and the macroscopically observed strains (R.C. Hurley has verified this in a 2017 paper). Parameters ---------- points: m x 3 numpy array M Particles' coordinates (in deformed configuration for strain field) connectivity: n x 4 numpy array Delaunay triangulation connectivity generated by spam.mesh.triangulate for example tetField: n x 3 x 3 numpy array Any field defined on tetrahedra (e.g., Bagi strains from bagiStrain). Returns ------- grainField: m x 3 x 3 array containing (3x3) arrays of strain Example ------- grainStrain = spam.mesh.projectBagiStrainToGrains(connectivity,bagiStrain[0],points) Returns strains for each grain. Notes ------ Function contributed by Ryan Hurley (Johns Hopkins University) """ # grainStrainVoigt: Ng array containing (6x1) arrays of strain in Voigt notation. # RCH Oct 2018. # from progressbar import ProgressBar # pbar = ProgressBar() # print("spam.mesh.projectTetFieldToGrains(): Pre-computing volumes...", end='') tetVolumes2 = spam.mesh.tetVolumes(points, connectivity) # print("done.") # Initialize list of grain values grainField = numpy.zeros(([points.shape[0]] + list(tetField.shape[1:])), dtype="<f4") # Get all the unique grain IDs in the Deluanay triangulation # print("spam.mesh.projectTetFieldToGrains(): Projecting tetrahedal to grains...") # Loop through grains... # for label in pbar(range(points.shape[0])): for label in range(points.shape[0]): # print("label = ", label) # Get the list of tets for which this label is a node touchingTets = numpy.where(connectivity == label)[0] volTot = 0.0 fieldTot = numpy.zeros(list(tetField.shape[1:]), dtype="<f4") # Loop through list of tets, summing the strains for touchingTet in touchingTets: # print("\ttet = ", touchingTet) vol = tetVolumes2[touchingTet] # Track total volume volTot += vol # Add volume-weighted field fieldTot += tetField[touchingTet] * vol # Divide strainTot by volTot fieldTot = fieldTot / volTot # Store in particles grainField[label] = fieldTot return grainField
[docs] def BCFieldFromDVCField( points, dvcField, mask=None, pixelSize=1.0, meshType="cube", centre=[0, 0], radius=1.0, topBottom=False, tol=1e-6, neighbours=4, ): """ This function imposes boundary conditions coming from a DVC result to the nodes of an unstructured FE mesh. Parameters ---------- points: 2D numpy array Array of ``n`` node positions of the unstructured mesh Each line is [nodeNumber, z, y, x] dvcField: 2D numpy array Array of ``m`` points of the dvc field Each line is [zPos, yPos, xPos, zDisp, yDisp, xDisp] mask: 2D numpy array, optional Boolean array of ``m`` points of the dvc field Points with 0 will be ignored for the field interpolation Default = None (`i.e.` interpolate based on all of the dvc points) pixelSize: float, optional physical size of a pixel (`i.e.` 1mm/px) Default = 1.0 meshType: string, optional For the moment, valid inputs are ``cube`` and ``cylinder`` The axis of a cylinder is considered to be ``z`` Note that if a cylindrical mesh is passed, ``centre`` and ``radius`` are highly recommended Default = `cube` centre: float, optional The centre of the cylinder [y, x] in physical units (`i.e.` mm) Default = [0, 0] radius: float, optional The radius of the cylinder in physical units (`i.e.` mm) Default = 1.0 topBottom: bool, optional If boundary conditions are passed only for the top (`i.e.` z=zmax) and bottom (`i.e.` z=zmin) surfaces of the mesh Default = False tol: float, optional Numerical tolerance for floats equality Default = 1e-6 neighbours: int, , optional Neighbours for field interpolation Default = 4 Returns ------- bc: 2D numpy array Boundary node displacements Each line is [nodeNumber, zPos, yPos, xPos, zDisp, yDisp, xDisp] WARNING ------- 1. All coordinates and displacement arrays are ``z``, ``y``, ``x`` 2. The axis of a cylinder is considered to be ``z`` """ # STEP 1: find the edge nodes posMax = [points[:, i].max() for i in range(1, 4)] posMin = [points[:, i].min() for i in range(1, 4)] bcNodes = [] if meshType == "cube": # extract edge nodes from the mesh for point in points: if topBottom: testMin = abs(point[1] - posMin[0]) < tol testMax = abs(point[1] - posMax[0]) < tol else: testMin = abs(point[1] - posMin[0]) < tol or abs(point[2] - posMin[1]) < tol or abs(point[3] - posMin[2]) < tol testMax = abs(point[1] - posMax[0]) < tol or abs(point[2] - posMax[1]) < tol or abs(point[3] - posMax[2]) < tol if testMin or testMax: bcNodes.append(point) elif meshType == "cylinder": # extract edge nodes from the mesh for point in points: testZ = abs(point[1] - posMin[0]) < tol or abs(point[1] - posMax[0]) < tol testXY = False if not topBottom: testXY = (point[2] - centre[0]) ** 2 + (point[3] - centre[1]) ** 2 >= (radius - tol) ** 2 if testZ or testXY: bcNodes.append(point) bcNodes = numpy.array(bcNodes) m = bcNodes.shape[0] # STEP 2: convert dvc field to physical unit dvcField *= pixelSize # STEP 3: Interpolate the disp values of FE using a weighted influence of the k nearest neighbours from DVC coord bcDisp = numpy.zeros((m, 3)) # create the k-d tree of the coordinates of DVC good points if mask is None: mask = numpy.ones(dvcField.shape[0]) goodPoints = numpy.where(mask == 1) treeCoord = scipy.spatial.KDTree(dvcField[:, :3][goodPoints]) for point in range(m): distance, ind = treeCoord.query(bcNodes[point, 1:], k=neighbours) # Check if we've hit the same point if numpy.any(distance == 0): bcDisp[point, 0] = dvcField[goodPoints][ind][numpy.where(distance == 0)][0, 3] bcDisp[point, 1] = dvcField[goodPoints][ind][numpy.where(distance == 0)][0, 4] bcDisp[point, 2] = dvcField[goodPoints][ind][numpy.where(distance == 0)][0, 5] else: weightSumInv = sum(1 / distance) # loop over neighbours for neighbour in range(neighbours): # calculate its weight weightInv = (1 / distance[neighbour]) / float(weightSumInv) # replace the disp values the weighted disp components of the ith nearest neighbour bcDisp[point, 0] += dvcField[goodPoints][ind[neighbour], 3] * weightInv bcDisp[point, 1] += dvcField[goodPoints][ind[neighbour], 4] * weightInv bcDisp[point, 2] += dvcField[goodPoints][ind[neighbour], 5] * weightInv # return node number and displacements return numpy.hstack((bcNodes, bcDisp))
[docs] def tetVolumes(points, connectivity): """ This function computes volumes of the tetrahedra passed with a connectivity matrix. Using algorithm in https://en.wikipedia.org/wiki/Tetrahedron#Volume Parameters ---------- points : Nx3 array Array of ``N`` coordinates of the points connectivity : Mx4 array Array of ``M`` none numbers that are connected as tetrahedra (e.g., the output from triangulate) Returns ------- volumes : vector of length M Volumes of tetrahedra Note ----- Pure python function. """ # Sanity checks on lengths: # if connectivity.shape[1] != 4 or points.shape[1] != 3 or connectivity.max() > points.shape[0]: # print("spam.mesh.tetVolumes(): Dimensionality problem, not running") # print("connectivity.max()", connectivity.max(), "points.shape[0]", points.shape[0]) # return volumes = numpy.zeros(connectivity.shape[0], dtype="<f4") connectivity = connectivity.astype(numpy.uint) # loop through tetrahedra for tetNumber in range(connectivity.shape[0]): fourNodes = connectivity[tetNumber] if max(fourNodes) >= points.shape[0]: print("spam.mesh.unstructured.tetVolumes(): this tet has node > points.shape[0], skipping.") else: if numpy.isfinite(points[fourNodes]).sum() != 3 * 4: print("spam.mesh.unstructured.bagiStrain(): nans in position, skipping") else: a = points[fourNodes[0]] b = points[fourNodes[1]] c = points[fourNodes[2]] d = points[fourNodes[3]] volumes[tetNumber] = numpy.abs(numpy.dot((a - d), numpy.cross((b - d), (c - d)))) / 6.0 return volumes
# def create2Patche(lengths, mesh_size_1, mesh_size_2, origin=[0., 0., 0.], verbose=False, gmshFile=None, vtkFile=None): # import pygmsh # import meshio # # lx, ly, lz = lengths # ox, oy, oz = origin # # # We will switch the input arrays from zyx to xyz before passing them to pygmsh # lengths = lengths[::-1] # origin = origin[::-1] # # # raw code # code = [] # # code.append("SetFactory(\"OpenCASCADE\");") # code.append("Mesh.RandomSeed = 0.5;") # code.append("Point(1) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox, y=oy, z=oz, mesh_size=mesh_size_1)) # code.append("Point(2) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx, y=oy, z=oz, mesh_size=mesh_size_1)) # code.append("Point(3) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx, y=oy+ly, z=oz, mesh_size=mesh_size_1)) # code.append("Point(4) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox, y=oy+ly, z=oz, mesh_size=mesh_size_1)) # code.append("Point(5) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox, y=oy, z=oz+lz, mesh_size=mesh_size_1)) # code.append("Point(6) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx, y=oy, z=oz+lz, mesh_size=mesh_size_1)) # code.append("Point(7) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx, y=oy+ly, z=oz+lz, mesh_size=mesh_size_1)) # code.append("Point(8) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox, y=oy+ly, z=oz+lz, mesh_size=mesh_size_1)) # code.append("Line(1) = {1,2};") # code.append("Line(2) = {2,3};") # code.append("Line(3) = {3,4};") # code.append("Line(4) = {4,1};") # code.append("Line(5) = {5,6};") # code.append("Line(6) = {6,7};") # code.append("Line(7) = {7,8};") # code.append("Line(8) = {8,5};") # code.append("Line(9) = {5,1};") # code.append("Line(10) = {2,6};") # code.append("Line(11) = {7,3};") # code.append("Line(12) = {8,4};") # code.append("Line Loop(13) = { 7, 8, 5, 6 };") # code.append("Plane Surface(14) = {13};") # code.append("Line Loop(15) = {1, 2, 3, 4};") # code.append("Plane Surface(16) = {15};") # code.append("Line Loop(17) = {8, 9, -4, -12};") # code.append("Plane Surface(18) = {17};") # code.append("Line Loop(19) = {1, 10, -5, 9};") # code.append("Plane Surface(20) = {19};") # code.append("Line Loop(21) = {10, 6, 11, -2};") # code.append("Plane Surface(22) = {21};") # code.append("Line Loop(23) = {11, 3, -12, -7};") # code.append("Plane Surface(24) = {23};") # code.append("Surface Loop(25) = {14, 24, 22, 20, 16, 18};") # code.append("Volume(26) = {25};") # code2 = code[:] # this one is for auxiliary (coarse) patch # code2.append("Point(28) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx/2, y=oy+ly/2, z=oz+lz/2, mesh_size=mesh_size_2)) # code2.append("Point{28} In Volume{26};") # e = 1e-6 #### this part is to enforce periodicity conditions for the patch mes # code.append("back_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz+lz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code2.append("back_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz+lz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code.append("front_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+e)) # code2.append("front_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+e)) # code.append("left_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code2.append("left_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code.append("right_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox+lx-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code2.append("right_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox+lx-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code.append("down_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+e,zmax=oz+lz+e)) # code2.append("down_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+e,zmax=oz+lz+e)) # code.append("up_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy+ly-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code2.append("up_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy+ly-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code.append("Periodic Surface{ right_surface() } = { left_surface() } Translate{ "+str(lx)+",0,0 };") # code2.append("Periodic Surface{ right_surface() } = { left_surface() } Translate{ "+str(lx)+",0,0 };") # code.append("Periodic Surface{ up_surface() } = { down_surface() } Translate{ 0,"+str(ly)+",0 };") # code2.append("Periodic Surface{ up_surface() } = { down_surface() } Translate{ 0,"+str(ly)+",0 };") # code.append("Periodic Surface{ back_surface() } = { front_surface() } Translate{ 0,0,"+str(lz)+" };") # code2.append("Periodic Surface{ back_surface() } = { front_surface() } Translate{ 0,0,"+str(lz)+" };") # # # geom = pygmsh.opencascade.Geometry(characteristic_length_min=mesh_size, characteristic_length_max=mesh_size,) # # geom = pygmsh.geo.Geometry() # geom = pygmsh.opencascade.Geometry() # geom2 = pygmsh.opencascade.Geometry() # # # add raw code to geometry # geom.add_raw_code(code) # geom2.add_raw_code(code2) # # # mesh # # points, cells, _, _, _ = geom.generate_mesh(verbose=verbose, extra_gmsh_arguments=["-optimize_netgen"]) # # points, cells, _, _, _ = geom.generate_mesh(verbose=verbose, extra_gmsh_arguments=["-optimize","-algo","del3d","-clmin",str(mesh_size),"-clmax",str(mesh_size)]) # mesh = geom.generate_mesh(verbose=verbose, extra_gmsh_arguments=["-3", "-optimize", "-algo", "del3d"]) # points = mesh.points # cells = mesh.cells # connectivity = cells['tetra'] # meshaux = geom2.generate_mesh(verbose=verbose, extra_gmsh_arguments=["-3", "-optimize", "-algo", "del3d"]) # pointsaux = meshaux.points # cellsaux = meshaux.cells # connectivityaux = cellsaux['tetra'] # # # write gmsh/vtk file # if gmshFile is not None: # meshio.write_points_cells("{}_fin.msh".format(gmshFile), points, cells, file_format='gmsh22') # meshio.write_points_cells("{}_aux.msh".format(gmshFile), pointsaux, {'tetra': connectivityaux}, file_format='gmsh22') # if vtkFile is not None: # meshio.write_points_cells("{}_fin.vtk".format(vtkFile), points, {'tetra': connectivity}, file_format='vtk') # meshio.write_points_cells("{}_aux.vtk".format(vtkFile), pointsaux, {'tetra': connectivityaux}, file_format='vtk') # # ### TEST pour 1 translation de lx (ox = ox + lx) dans la direction x # # on veut produire : # ### 2 fichiers msh "fin" domain_fin_i.msh (et aussi domain_fin_i.vtk pour la visu) # ### 2 fichiers msh "aux" domain_aux_i.msh (et aussi vtk pour la visu) # ### 1 fichier msh "global" qui reunit les deux fichiers aux (et aussi vtk pour la visu) # points[:, 0] += lx # pour les fichiers "fin" # # temppoints = copy.deepcopy(pointsaux) # pointsaux[:, 0] += lx #translate aux mesh # glob_points = numpy.concatenate((temppoints,pointsaux), axis = 0) #create an array for global mesh (union of aux meshes) # tempconnec = copy.deepcopy(connectivityaux) # connectivityaux[:, :] += pointsaux.shape[0] #translate aux connectivity # glob_connectivity = numpy.concatenate((tempconnec,connectivityaux), axis = 0) #create an array for global mesh (union of aux meshes) # # # write gmsh/vtk file for second fine and aux mesh # if gmshFile is not None: # meshio.write_points_cells("{}_fin_2.msh".format(gmshFile), points, cells, file_format='gmsh22') # meshio.write_points_cells("{}_aux_2.msh".format(gmshFile), pointsaux, {'tetra': tempconnec}, file_format='gmsh22') # if vtkFile is not None: # meshio.write_points_cells("{}_fin_2.vtk".format(vtkFile), points, {'tetra': connectivity}, file_format='vtk') # meshio.write_points_cells("{}_aux_2.vtk".format(vtkFile), pointsaux, {'tetra': tempconnec}, file_format='vtk') # attention ici on ne decale pas la connectivité # # # now try to generate global mesh # # its a three step process # # first, make a list of a list of all nodes that appear more than once in glob_points. # # second, replace each one of those "double" node by the smaller number in the connectivity (glob_connectivity). At this stage all those double nodes are now unlinked to any element. # # third, remove the double nodes from the node list and modify the connectivity accordingly (most difficult step!) # # # print('Start to build GLOBAL mesh - step 1') # double = [] # for i,node1 in enumerate(glob_points): # look at every point in the mesh # for j,node2 in enumerate(glob_points[i+1:]): #and check for existing node at the same place # if ((node1[0]==node2[0]) and (node1[1]==node2[1]) and (node1[2]==node2[2])): # print('Finding double node of coordinates: ', i+1, '=',glob_points[i], j+i+2, '=',glob_points[j+i+1]) # double.append(i+1) # double.append(j+i+2) # # # print('Start to build GLOBAL mesh - step 2') # #here we should replace the nodes written in the double list in the glob_connectivity # for node1,node2 in zip(double[0::2], double[1::2]): # for k,elem in enumerate(glob_connectivity): # if elem[0] == node2-1: # print('Replacing double node ', node2, ' by ', node1, 'in element ', k+1, 'component ', 1) # glob_connectivity[k][0] = node1-1 # if elem[1] == node2-1: # print('Replacing double node ', node2, ' by ', node1, 'in element ', k+1, 'component ', 2) # glob_connectivity[k][1] = node1-1 # if elem[2] == node2-1: # print('Replacing double node ', node2, ' by ', node1, 'in element ', k+1, 'component ', 3) # glob_connectivity[k][2] = node1-1 # if elem[3] == node2-1: # print('Replacing double node ', node2, ' by ', node1, 'in element ', k+1, 'component ', 4) # glob_connectivity[k][3] = node1-1 # # print('Start to build GLOBAL mesh - step 3') # #here we should erase the double nodes written in the node list and shift -1 the glob_connectivity # toberemoved = [] # for node1,node2 in zip(double[0::2], double[1::2]): # if len(toberemoved) == 0: # toberemoved.append(node2) # elif toberemoved[-1] != node2: # toberemoved.append(node2) # toberemoved.sort(reverse=True) # # print('toberemoved : ', toberemoved) # for node in toberemoved: # glob_points = numpy.delete(glob_points, node-1,0) # Point removing # for k,elem in enumerate(glob_connectivity): # if elem[0] > node-1: # glob_connectivity[k][0] -= 1 # if elem[1] > node-1: # glob_connectivity[k][1] -= 1 # if elem[2] > node-1: # glob_connectivity[k][2] -= 1 # if elem[3] > node-1: # glob_connectivity[k][3] -= 1 # # meshio.write_points_cells("global.msh", glob_points, {'tetra': glob_connectivity}, file_format='gmsh22') # meshio.write_points_cells("global.vtk", glob_points, {'tetra': glob_connectivity}, file_format='vtk') # # # return # # # def create8Patche(lengths, mesh_size_1, mesh_size_2, origin=[0., 0., 0.], verbose=False, gmshFile=None, vtkFile=None): # # NOT USED # import pygmsh # import meshio # # lx, ly, lz = lengths # ox, oy, oz = origin # # # We will switch the input arrays from zyx to xyz before passing them to pygmsh # lengths = lengths[::-1] # origin = origin[::-1] # # # raw code # code = [] # # code.append("SetFactory(\"OpenCASCADE\");") # code.append("Mesh.RandomSeed = 0.5;") # code.append("Point(1) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox, y=oy, z=oz, mesh_size=mesh_size_1)) # code.append("Point(2) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx, y=oy, z=oz, mesh_size=mesh_size_1)) # code.append("Point(3) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx, y=oy+ly, z=oz, mesh_size=mesh_size_1)) # code.append("Point(4) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox, y=oy+ly, z=oz, mesh_size=mesh_size_1)) # code.append("Point(5) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox, y=oy, z=oz+lz, mesh_size=mesh_size_1)) # code.append("Point(6) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx, y=oy, z=oz+lz, mesh_size=mesh_size_1)) # code.append("Point(7) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx, y=oy+ly, z=oz+lz, mesh_size=mesh_size_1)) # code.append("Point(8) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox, y=oy+ly, z=oz+lz, mesh_size=mesh_size_1)) # code.append("Line(1) = {1,2};") # code.append("Line(2) = {2,3};") # code.append("Line(3) = {3,4};") # code.append("Line(4) = {4,1};") # code.append("Line(5) = {5,6};") # code.append("Line(6) = {6,7};") # code.append("Line(7) = {7,8};") # code.append("Line(8) = {8,5};") # code.append("Line(9) = {5,1};") # code.append("Line(10) = {2,6};") # code.append("Line(11) = {7,3};") # code.append("Line(12) = {8,4};") # code.append("Line Loop(13) = { 7, 8, 5, 6 };") # code.append("Plane Surface(14) = {13};") # code.append("Line Loop(15) = {1, 2, 3, 4};") # code.append("Plane Surface(16) = {15};") # code.append("Line Loop(17) = {8, 9, -4, -12};") # code.append("Plane Surface(18) = {17};") # code.append("Line Loop(19) = {1, 10, -5, 9};") # code.append("Plane Surface(20) = {19};") # code.append("Line Loop(21) = {10, 6, 11, -2};") # code.append("Plane Surface(22) = {21};") # code.append("Line Loop(23) = {11, 3, -12, -7};") # code.append("Plane Surface(24) = {23};") # code.append("Surface Loop(25) = {14, 24, 22, 20, 16, 18};") # code.append("Volume(26) = {25};") # code2 = code[:] # this one is for auxiliary (coarse) patch # code2.append("Point(28) = {{ {x}, {y}, {z}, {mesh_size} }};".format(x=ox+lx/2, y=oy+ly/2, z=oz+lz/2, mesh_size=mesh_size_2)) # code2.append("Point{28} In Volume{26};") # e = 1e-6 #### this part is to enforce periodicity conditions for the patch mes # code.append("back_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz+lz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code2.append("back_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz+lz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code.append("front_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+e)) # code2.append("front_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+e)) # code.append("left_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code2.append("left_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code.append("right_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox+lx-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code2.append("right_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox+lx-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code.append("down_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+e,zmax=oz+lz+e)) # code2.append("down_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+e,zmax=oz+lz+e)) # code.append("up_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy+ly-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code2.append("up_surface() = Surface In BoundingBox{{{xmin},{ymin},{zmin},{xmax},{ymax},{zmax}}};".format(xmin=ox-e,ymin=oy+ly-e,zmin=oz-e,xmax=ox+lx+e,ymax=oy+ly+e,zmax=oz+lz+e)) # code.append("Periodic Surface{ right_surface() } = { left_surface() } Translate{ "+str(lx)+",0,0 };") # code2.append("Periodic Surface{ right_surface() } = { left_surface() } Translate{ "+str(lx)+",0,0 };") # code.append("Periodic Surface{ up_surface() } = { down_surface() } Translate{ 0,"+str(ly)+",0 };") # code2.append("Periodic Surface{ up_surface() } = { down_surface() } Translate{ 0,"+str(ly)+",0 };") # code.append("Periodic Surface{ back_surface() } = { front_surface() } Translate{ 0,0,"+str(lz)+" };") # code2.append("Periodic Surface{ back_surface() } = { front_surface() } Translate{ 0,0,"+str(lz)+" };") # # # geom = pygmsh.opencascade.Geometry(characteristic_length_min=mesh_size, characteristic_length_max=mesh_size,) # # geom = pygmsh.geo.Geometry() # geom = pygmsh.opencascade.Geometry() # geom2 = pygmsh.opencascade.Geometry() # # # add raw code to geometry # geom.add_raw_code(code) # geom2.add_raw_code(code2) # # # mesh # # points, cells, _, _, _ = geom.generate_mesh(verbose=verbose, extra_gmsh_arguments=["-optimize_netgen"]) # # points, cells, _, _, _ = geom.generate_mesh(verbose=verbose, extra_gmsh_arguments=["-optimize","-algo","del3d","-clmin",str(mesh_size),"-clmax",str(mesh_size)]) # mesh = geom.generate_mesh(verbose=verbose, extra_gmsh_arguments=["-3", "-optimize", "-algo", "del3d"]) # points = mesh.points # cells = mesh.cells # connectivity = cells['tetra'] # meshaux = geom2.generate_mesh(verbose=verbose, extra_gmsh_arguments=["-3", "-optimize", "-algo", "del3d"]) # pointsaux = meshaux.points # cellsaux = meshaux.cells # connectivityaux = cellsaux['tetra'] # # ### TEST pour 7 translations # # on veut produire : # ### 8 fichiers msh "fin" domain_fin_i.msh (et aussi domain_fin_i.vtk pour la visu) # ### 8 fichiers msh "aux" domain_aux_i.msh (et aussi vtk pour la visu) # ### 1 fichier msh "global" qui reunit les deux fichiers aux (et aussi vtk pour la visu) # shifts = [[0, 0, 0], [lx, 0, 0], [0, ly, 0], [-lx, 0, 0], [0, -ly, lz], [lx, 0, 0], [0, ly, 0], [-lx, 0, 0]] # glob_points = copy.deepcopy(pointsaux) # glob_connectivity = copy.deepcopy(connectivityaux) # connectivityaux_init = copy.deepcopy(connectivityaux) # for i,shift in enumerate(shifts): # points[:, 0] += shift[0] # pour les fichiers "fin" # points[:, 1] += shift[1] # pour les fichiers "fin" # points[:, 2] += shift[2] # pour les fichiers "fin" # # pointsaux[:, 0] += shift[0] #translate aux mesh # pointsaux[:, 1] += shift[1] #translate aux mesh # pointsaux[:, 2] += shift[2] #translate aux mesh # glob_points = numpy.concatenate((glob_points,copy.deepcopy(pointsaux)), axis = 0) #create an array for global mesh (union of aux meshes) # connectivityaux[:, :] += pointsaux.shape[0] # glob_connectivity = numpy.concatenate((glob_connectivity, copy.deepcopy(connectivityaux)), axis = 0) # # write gmsh/vtk file for fine and aux mesh # if gmshFile is not None: # meshio.write_points_cells("patch_fin_"+str(i+1)+".msh", points, cells, file_format='gmsh22') # meshio.write_points_cells("patch_aux_"+str(i+1)+".msh", pointsaux, {'tetra': connectivityaux_init}, file_format='gmsh22') # if vtkFile is not None: # meshio.write_points_cells("patch_fin_"+str(i+1)+".vtk", points, {'tetra': connectivity}, file_format='vtk') # meshio.write_points_cells("patch_aux_"+str(i+1)+".vtk", pointsaux, {'tetra': connectivityaux_init}, file_format='vtk') # attention ici on ne decale pas la connectivité # # # now try to generate global mesh # # its a three step process # # first, make a list of a list of all nodes that appear more than once in glob_points. # # second, replace each one of those "double" node by the smaller number in the connectivity (glob_connectivity). At this stage all those double nodes are now unlinked to any element. # # third, remove the double nodes from the node list and modify the connectivity accordingly (most difficult step!) # # # print('Start to build GLOBAL mesh - step 1') # double = [] # for i,node1 in enumerate(glob_points): # look at every point in the mesh # for j,node2 in enumerate(glob_points[i+1:]): #and check for existing node at the same place # if ((node1[0]==node2[0]) and (node1[1]==node2[1]) and (node1[2]==node2[2])): # print('Finding double node of coordinates: ', i+1, '=',glob_points[i], j+i+2, '=',glob_points[j+i+1]) # double.append(i+1) # double.append(j+i+2) # # # print('Start to build GLOBAL mesh - step 2') # #here we should replace the nodes written in the double list in the glob_connectivity # for node1,node2 in zip(double[0::2], double[1::2]): # for k,elem in enumerate(glob_connectivity): # if elem[0] == node2-1: # print('Replacing double node ', node2, ' by ', node1, 'in element ', k+1, 'component ', 1) # glob_connectivity[k][0] = node1-1 # if elem[1] == node2-1: # print('Replacing double node ', node2, ' by ', node1, 'in element ', k+1, 'component ', 2) # glob_connectivity[k][1] = node1-1 # if elem[2] == node2-1: # print('Replacing double node ', node2, ' by ', node1, 'in element ', k+1, 'component ', 3) # glob_connectivity[k][2] = node1-1 # if elem[3] == node2-1: # print('Replacing double node ', node2, ' by ', node1, 'in element ', k+1, 'component ', 4) # glob_connectivity[k][3] = node1-1 # # # print('Start to build GLOBAL mesh - step 3') # # #here we should erase the double nodes written in the node list and shift -1 the glob_connectivity # toberemoved = [] # for node1,node2 in zip(double[0::2], double[1::2]): # if len(toberemoved) == 0: # toberemoved.append(node2) # elif toberemoved[-1] != node2: # toberemoved.append(node2) # toberemoved.sort(reverse=True) # # print('toberemoved : ', toberemoved) # for node in toberemoved: # glob_points = numpy.delete(glob_points, node-1,0) # Point removing # for k,elem in enumerate(glob_connectivity): # if elem[0] > node-1: # glob_connectivity[k][0] -= 1 # if elem[1] > node-1: # glob_connectivity[k][1] -= 1 # if elem[2] > node-1: # glob_connectivity[k][2] -= 1 # if elem[3] > node-1: # glob_connectivity[k][3] -= 1 # # meshio.write_points_cells("global.msh", glob_points, {'tetra': glob_connectivity}, file_format='gmsh22') # meshio.write_points_cells("global.vtk", glob_points, {'tetra': glob_connectivity}, file_format='vtk') # # # return
[docs] def rankPoints(points, neighbourRadius=20, verbose=True): """ This function ranks an array of points around the top point Parameters ---------- points: numpy array N x 3 Coordinates (zyx) of the points neighbourRadius: float, optional Distance from the current point to include neighbours If no neighbour is found, then the closest point is taken Default: 20 Returns ------- pointsRanked: numpy array N x 3 Coordinates (zyx) of the ranked points rowNumbers : 1D numpy array N of ints Reorganised row numbers from input Note ----- Based on https://gricad-gitlab.univ-grenoble-alpes.fr/DVC/pt4d """ rowNumbers = numpy.zeros((points.shape[0]), dtype=int) points = numpy.array([points[:, 0], points[:, 1], points[:, 2], numpy.arange(points.shape[0])]).T # counters p = pR = 0 # create the ranked array, first ranked point is the top point of the input array pointsRanked = numpy.zeros_like(points) pointsRanked[0] = points[0] # remove ranked point from input array points = numpy.delete(points, 0, 0) # Create progressbar numberOfPoints = pointsRanked.shape[0] if verbose: widgets = [ progressbar.FormatLabel(""), " ", progressbar.Bar(), " ", progressbar.AdaptiveETA(), ] pbar = progressbar.ProgressBar(widgets=widgets, maxval=numberOfPoints) pbar.start() while points.shape[0] >= 1: # Try to see how many points can be found around the current point based on the given radius treeCoord = scipy.spatial.KDTree(points[:, 0:3]) indRadius = numpy.array(treeCoord.query_ball_point(pointsRanked[pR, 0:3], neighbourRadius)) indRadius = indRadius[numpy.argsort(indRadius)] # if no points inside the given radius, just find the closest point if len(indRadius) < 1: distance, ind = treeCoord.query(pointsRanked[pR, 0:3], k=1) indRadius = numpy.array([ind]) # fill in the ranked array with the point(s) found pointsRanked[p + 1 : p + 1 + len(indRadius)] = points[indRadius] for qn, q in enumerate(range(p + 1, p + 1 + len(indRadius))): rowNumbers[int(points[indRadius[qn], -1])] = q # remove ranked point(s) from input array points = numpy.delete(points, indRadius, 0) # update counters p += len(indRadius) pR += 1 if verbose: widgets[0] = progressbar.FormatLabel("{:.1f}%%".format((p / numberOfPoints) * 100)) pbar.update(pR) return pointsRanked[:, 0:3], rowNumbers