Create a mesh with boundary markers in Python, Fenics - python

My aim is to create a mesh in Python and setting markers on some 1-dimensional subset.
Up until now, I have always creates a set, for example a rectangle in gmsh, then put for example a circle in it. Then gmsh puts a mesh on my structure and I can mark the boundary of the rectangle and of the circle as facets (as xdmf files). I can then let Python read my mesh and boundary facets and using it, f.e. to solve PDEs.
What I want to do now is the following: I still want to have my rectangle with a mesh, but instead of defining the facet markes in gmsh, I want to define them using the image of a function.
More precicely: Instead of creating a circle in gmsh, I want to consider, for example, the function
Then I want to use as my facet and mark it in my mesh.
Is there a way to do this? I feel kind of lost here.

I am not sure if I understood everything in your question correctly, but here is some code that might help you. One thing I do not know is how to add element edges to physical groups in gmsh. But maybe you can figure that out.
So here is the code:
import gmsh
import numpy as np
def mapping(point):
x = point[0]
y = point[1]
z = point[2]
result = [2*x,3*y,z]
return result
def inverseMapping(point):
x = point[0]
y = point[1]
z = point[2]
result = [(1/2)*x,(1/3)*y,z]
return result
def getNodes():
nodeTags, nodeCoord, _ = gmsh.model.mesh.getNodes()
nodeCoord = np.reshape(nodeCoord,(len(nodeTags),3))
return nodeTags, nodeCoord
def getEdgeNodeCoordinates():
edgeTags, edgeNodes = gmsh.model.mesh.getAllEdges()
edgeNodes = np.reshape(edgeNodes,(len(edgeTags),2))
nodeTags, nodeCoord = getNodes()
coord = []
for i in range(0,len(edgeTags)):
tagNode1 = edgeNodes[i][0]
tagNode2 = edgeNodes[i][1]
nodeIndex1 = list(nodeTags).index(tagNode1)
nodeIndex2 = list(nodeTags).index(tagNode2)
nodeCoord1 = nodeCoord[nodeIndex1]
nodeCoord2 = nodeCoord[nodeIndex2]
coord.append([nodeCoord1,nodeCoord2])
return edgeTags, edgeNodes, nodeTags, coord
def getInverseNodeCoordinates(edgeNodeCoordinates):
coord = []
for edgeNodes in edgeNodeCoordinates:
nodeCoord1 = edgeNodes[0]
nodeCoord2 = edgeNodes[1]
newCoord1 = inverseMapping(nodeCoord1)
newCoord2 = inverseMapping(nodeCoord2)
coord.append([newCoord1, newCoord2])
return coord
def checkIntersection(edgeTags, edgeNodeCoordinates, inverseCoordinates):
intersectingEdgeTags = []
intersectingEdgeNodeCoord = []
# 1 = inside, 0 = outside
for i in range(0,len(inverseCoordinates)):
pair = inverseCoordinates[i]
coord1 = pair[0]
coord2 = pair[1]
e1 = 1 if np.linalg.norm(coord1) <= 1 else 0
e2 = 1 if np.linalg.norm(coord2) <= 1 else 0
s = e1 + e2 # s = 0 --> both nodes outside of manifold
# s = 1 --> one node inside and one node outside of manifold
# s = 2 --> both nodes inside of manifold
if s == 1:
intersectingEdgeTags.append(edgeTags[i])
intersectingEdgeNodeCoord.append(edgeNodeCoordinates[i])
return intersectingEdgeTags, intersectingEdgeNodeCoord
def visualizeEdges(intersectingEdgeNodeCoord):
for pair in intersectingEdgeNodeCoord:
p1 = pair[0]
p2 = pair[1]
t1 = gmsh.model.occ.addPoint(p1[0],p1[1],p1[2])
t2 = gmsh.model.occ.addPoint(p2[0],p2[1],p2[2])
line = gmsh.model.occ.addLine(t1, t2)
gmsh.model.occ.synchronize()
gmsh.model.setColor([(1,line)], 255, 0, 0)
gmsh.model.occ.synchronize()
gmsh.initialize()
# Create a rectangle which will be meshed later.
tag_vol_1 = gmsh.model.occ.addRectangle(-3, -4, 0, 6, 8)
# Sample the S1 manifold with n_points
S1_sampling_points = []
n_points = 100
maxAngle = 2*np.pi
angle = maxAngle/n_points
z = 0
for i in range(0,n_points):
x = np.cos(i*angle)
y = np.sin(i*angle)
S1_sampling_points.append([x,y,z])
# Map the sampled S1 points to 2*x, 3*y, z.
# This is only for "visualization" via a spline.
mappedPoints = []
mappedPointTags = []
for point in S1_sampling_points:
mappedPoint = mapping(point)
tag = gmsh.model.occ.addPoint(mappedPoint[0], mappedPoint[1], mappedPoint[2])
mappedPointTags.append(tag)
# Here the spline fitting is performed
# You will see it visualized when gmsh opens.
tagMappedS1 = gmsh.model.occ.addSpline(mappedPointTags + [mappedPointTags[0]]) # make the spline periodic by setting the last point equal to the first one
gmsh.model.occ.synchronize()
# Mesh the rectangle and tell gmsh to create edges which we can access.
gmsh.model.mesh.generate(2)
gmsh.model.mesh.createEdges() # You need to call this before using gmsh.model.mesh.getAllEdges()
# Get all these self-explanatory things
edgeTags, edgeNodes, nodeTags, edgeNodeCoordinates = getEdgeNodeCoordinates()
# Calculate the inverse-mapped coordinates of all nodes.
# With this we can just check if the transformed nodes are inside a circle of radius 1 or outside.
# If for every egde one node is inside, and the other node is outside the circle, then the edge is
# intersected by the mapped manifold f(S1) --> (2*x, 3*y, z). We then save the tag of such an edge.
inverseCoordinates = getInverseNodeCoordinates(edgeNodeCoordinates)
intersectingEdgeTags, intersectingEdgeNodeCoord = checkIntersection(edgeTags, edgeNodeCoordinates, inverseCoordinates)
# Here all intersecting edges are created within gmsh so you can see it.
# This is for visualization only. A lot of nodes with the same coordinates are created here twice.
visualizeEdges(intersectingEdgeNodeCoord)
gmsh.fltk.run()
gmsh.finalize()
And the result in gmsh looks like this:

Related

How to find a tour in a graph, that visits every node once, considering the condition that every enclosed angle in the tour is larger than 90°?

The input is a undirected, unweighted graph with about 80 nodes (or to be more specific: the coordinates of the nodes in a txt file), in which a route is to be found that visits each node once, but does not use any vertex twice. Furthermore, the included angle between each two vertexes of the route should be greater than 90°. On the left side of the following picture you can see an angle that is not wanted in the route, in contrast to the angle on the right side:
Furthermore, start and end points of the route do not have to be identical. The route does not have to be the shortest one but should be as short as possible.
Here's what I've tried so far:
Considering there are 80 nodes in the graph it would be impossible to use a depth-first-search or backtracking algorithm because it would just take too long. Instead, I've implemented a greedy-algorithm, which always makes the best decision at the time of the decision. It works well for most of the examples, however, some are just impssoible to solve for that type of algorithm. Here's my whole code, which uses the "read_coordinates(file)" function to import the coordinates (x- and y-coordinates of a point line by line). After doing that, the function "greedy_approach(coordinates)" tries to find a route.
import matplotlib.pyplot as plt
import numpy as np
import math
import matplotlib.animation as animation
def read_coordinates(file):
with open(file, "r") as f:
lines = f.readlines()
coordinates = []
for line in lines:
x, y = map(float, line.strip().split())
coordinates.append((x, y))
return coordinates
def check_angle(v1, v2):
cos_angle = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
if cos_angle > 1:
cos_angle = 1
elif cos_angle < -1:
cos_angle = -1
angle = math.acos(cos_angle)
return abs(angle) < math.pi / 2
def greedy_approach(coordinates):
all_visited = set(range(len(coordinates)))
overall_best_route = []
for start in range(len(coordinates)):
route = [coordinates[start]]
visited = {start}
unvisited = all_visited - visited
while unvisited:
best_route = None
best_distance = float("inf")
for i in unvisited:
point = coordinates[i]
# Vektor 1
v1 = np.array(route[-1]) - np.array(point)
if len(route) > 1:
# Vektor 2
v2 = np.array(route[-2]) - np.array(route[-1])
else:
v2 = np.array([1, 0])
if check_angle(v1, v2):
temp_route = route + [point]
temp_distance = route_length(temp_route)
if temp_distance < best_distance:
best_route = temp_route
best_distance = temp_distance
if not best_route:
break
route = best_route
visited.add(coordinates.index(route[-1]))
unvisited = all_visited - visited
if len(route) > len(overall_best_route):
overall_best_route = route
if len(visited) == len(coordinates):
return overall_best_route
return overall_best_route
# return None
def plot_route(coordinates, route):
x_coo = coordinates[:, 0]
y_coo = coordinates[:, 1]
x = [p[0] for p in route]
# x = [p[0] for p in route]
y = [p[1] for p in route]
fig, ax = plt.subplots()
ax.plot(x_coo, y_coo, 'o')
line, = ax.plot(x[:1], y[:1], '-')
def update(num):
num += 1
if num >= len(x) + 1:
ani.event_source.stop()
return line,
line.set_data(x[:num], y[:num])
return line,
ani = animation.FuncAnimation(fig, update, frames=len(x)+1, interval=100, blit=False)
ax.plot(x[0], y[0], 'go')
ax.plot(x[-1], y[-1], 'ro')
plt.show()
if __name__ == "__main__":
file = 'X:\coordinates6.txt'
coordinates = read_coordinates(file)
route = greedy_approach(coordinates)
plot_route(np.array(coordinates), route)
It goes without saying that you need to change the variable "file" accoring to the path, where you've saved the txt-file. In the following I've included the txt-file of such an "unsolveable" graph:
102.909291 60.107868
-89.453831 162.237392
64.943433 -119.784474
121.392544 56.694081
-107.196865 -77.792599
20.218290 88.031173
202.346980 -189.069699
143.114152 -135.866707
-144.887799 -73.495410
92.255820 -93.514104
-55.091518 198.826966
228.929427 82.624982
96.781707 141.370805
154.870684 140.327660
112.833346 -38.057607
14.005617 -14.015334
138.136997 -31.348808
73.689751 110.224271
100.006932 76.579303
120.906436 131.798810
21.067444 122.164599
49.091876 150.678826
85.043830 108.946389
-194.986965 101.363745
152.102728 -193.381252
238.583388 -133.143524
151.432196 121.427337
221.028639 -139.435079
-139.741580 57.936680
-72.565291 -24.281820
155.405344 -56.437901
58.019653 49.937906
277.821597 104.262606
19.765322 -99.236400
246.621634 101.705861
289.298882 56.051342
172.836936 59.184233
132.794476 135.681392
155.341949 -20.252779
134.692592 -102.152826
-97.391662 124.120512
245.415055 44.794067
255.134924 115.594915
83.005905 64.646774
245.020791 -167.448848
-102.699992 95.632069
-4.590656 -40.067226
-191.216327 -162.689024
210.186432 -127.403563
-51.343758 -57.654823
187.669263 -122.655771
121.661135 85.267672
46.674278 -193.090008
-189.988471 -98.043874
-175.118239 77.842636
-187.485329 -177.031237
56.716498 66.959624
-18.507391 -22.905270
-167.994506 138.195365
81.740403 10.276251
-19.310012 -131.810965
157.588994 -144.200765
40.327635 19.216022
-126.569816 -30.645224
150.526118 -88.230057
76.647124 -7.289705
231.944823 82.961057
58.716620 32.835930
-288.744132 -173.349893
-293.833463 -165.440105
-31.745416 -69.207960
175.677917 98.929343
216.825920 -152.024123
21.176627 -165.421555
-100.569041 140.808607
-90.160190 -25.200829
242.810288 -182.054289
-154.225945 -135.522059
102.223372 174.201904
64.559003 82.567627
I would really appreciate it if you could have a look into that problem :)
Every node has a potential edge to every other node. ( It might be possible to optimize things by specifying a cut-off distance so that two nodes that are far apart will not create an edge )
Now you need to split nodes so that you end up with nodes that only have two edges connecting them.
Now set a "cost" for each node which is 1 if the angle between the edges is more than the cut-off and "infinite" if smaller
Transform the nodes into edges ( with the cost ) and the edges into nodes.
Run the spanning tree algorithm on this graph.
This diagram shows how to split nodes so that they have only two ( real ) edges.
The red edges will have zero cost and be ignored when calculating the angle.

How to obtain surfaces tag in Gmsh Python api?

i am trying to generate geometries and meshes with the Python api of Gmsh, planning to use it in FEniCS.
I started creating my geometry following the steps reported here: https://jsdokken.com/src/tutorial_gmsh.html
The author first create the volume and then retrieve the surfaces with the command:
surfaces = gmsh.model.occ.getEntities(dim=2)
Finally, he is able to relate the surface to the tag simply by finding the center of mass (com). He uses the command gmsh.model.occ.getCenterOfMass(dim,tag) and compares it with the know com position of his surfaces, like this:
inlet_marker, outlet_marker, wall_marker, obstacle_marker = 1, 3, 5, 7
walls = []
obstacles = []
for surface in surfaces:
com = gmsh.model.occ.getCenterOfMass(surface[0], surface[1])
if np.allclose(com, [0, B/2, H/2]):
gmsh.model.addPhysicalGroup(surface[0], [surface[1]], inlet_marker)
inlet = surface[1]
gmsh.model.setPhysicalName(surface[0], inlet_marker, "Fluid inlet")
elif np.allclose(com, [L, B/2, H/2]):
gmsh.model.addPhysicalGroup(surface[0], [surface[1]], outlet_marker)
gmsh.model.setPhysicalName(surface[0], outlet_marker, "Fluid outlet")
elif np.isclose(com[2], 0) or np.isclose(com[1], B) or np.isclose(com[2], H) or np.isclose(com[1],0):
walls.append(surface[1])
else:
obstacles.append(surface[1])
Now, my problem is that this cannot work if two or more surfaces share the same com, such as two concentric cylinders.
How can i discriminate between them in such situation?
For example in case of an hollow cylinder, i would like to have a tag for each surface in order to apply different boundary conditions in FEniCS.
Thanks in advance!
You can make use of gmsh.model.getAdjacencies(dim,tag) where dim and tag are the dimension and tag of your entity of interest. This functions returns two lists up, down. The first one gives you the tags of all entities adjacent (=neighbouring) to the entity of interest with dimension dim + 1. The second list gives you the tags of all entities adjacent to the entity of interest with dimension dim - 1.
In 3D (i.e. dim = 3) the up list will be empty because there are no 4D structures in gmsh. The down list will contain all surface tags the boundary of the volume is made of.
Below is an example code. Part 1 is straight forward and in Part 2 I added a functions that sorts the surface tags by their x-coordinate.
import gmsh
gmsh.initialize()
## PART 1:
tag_cylinder_1 = gmsh.model.occ.addCylinder(0, 0, 0, 1, 0, 0, 0.1)
tag_cylinder_2 = gmsh.model.occ.addCylinder(0, 0, 0, 1, 0, 0, 0.2)
gmsh.model.occ.synchronize()
up_cyl_1, down_cyl_1 = gmsh.model.getAdjacencies(3,tag_cylinder_1)
up_cyl_2, down_cyl_2 = gmsh.model.getAdjacencies(3,tag_cylinder_2)
com_1 = gmsh.model.occ.getCenterOfMass(2, down_cyl_1[0])
com_2 = gmsh.model.occ.getCenterOfMass(2, down_cyl_1[1])
com_3 = gmsh.model.occ.getCenterOfMass(2, down_cyl_1[2])
## PART 2:
def calcBoxVolume(box):
dx = box[3] - box[0]
dy = box[4] - box[1]
dz = box[5] - box[2]
return dx*dy*dz
def getOrderedTags(cylTag):
up, down = gmsh.model.getAdjacencies(3,cylTag)
surf_COM = []
for surface in down:
com = [surface] + list(gmsh.model.occ.getCenterOfMass(2, surface))
surf_COM.append(com)
orderedSurfaces = sorted(surf_COM,key = lambda x: x[1])
orderedSurfaceTags = [item[0] for item in orderedSurfaces]
return orderedSurfaceTags
def setPhysicalTags(name,cylTag):
orderedSurfaces = getOrderedTags(cylTag)
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[0]],name="inlet_"+name)
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[1]],name="tube_"+name)
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[3]],name="outlet_"+name)
def setPhysicalTagsCylDiff(name,cylTag):
orderedSurfaces = getOrderedTags(cylTag)
tag_A = orderedSurfaces[1]
tag_B = orderedSurfaces[2]
box_tube_A = gmsh.model.getBoundingBox(2,tag_A)
box_tube_B = gmsh.model.getBoundingBox(2,tag_B)
volBoxA = calcBoxVolume(box_tube_A)
volBoxB = calcBoxVolume(box_tube_B)
if volBoxA > volBoxB:
innerTag = tag_B
outerTag = tag_A
else:
innerTag = tag_A
outerTag = tag_B
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[0]],name="inlet_"+name)
gmsh.model.addPhysicalGroup(2, [innerTag],name="tube_inner_"+name)
gmsh.model.addPhysicalGroup(2, [outerTag],name="tube_outer_"+name)
gmsh.model.addPhysicalGroup(2, [orderedSurfaces[3]],name="outlet_"+name)
# setPhysicalTags("Cylinder_1",tag_cylinder_1)
# setPhysicalTags("Cylinder_2",tag_cylinder_2)
outDimTags, outDimTagsMap = gmsh.model.occ.cut([(3,tag_cylinder_2)],[(3,tag_cylinder_1)])
cylDiffTag = outDimTags[0][1]
gmsh.model.occ.synchronize()
setPhysicalTagsCylDiff("CylDiff",cylDiffTag)
gmsh.model.mesh.generate(2)
gmsh.fltk.run()
gmsh.finalize()

How to use vtkOBBTree and IntersectWithLine to find the intersection of a line and a PolyDataFilter in python using vtk?

I have an oriented cylinder generated with vtkCylinderSource and some transformations are applied on it to get the orientation that i want. Here is the code for creating this oriented-cylinder:
def cylinder_object(startPoint, endPoint, radius, my_color="DarkRed", opacity=1):
colors = vtk.vtkNamedColors()
# Create a cylinder.
# Cylinder height vector is (0,1,0).
# Cylinder center is in the middle of the cylinder
cylinderSource = vtk.vtkCylinderSource()
cylinderSource.SetRadius(radius)
cylinderSource.SetResolution(50)
# Generate a random start and end point
# startPoint = [0] * 3
# endPoint = [0] * 3
rng = vtk.vtkMinimalStandardRandomSequence()
rng.SetSeed(8775070) # For testing.8775070
# Compute a basis
normalizedX = [0] * 3
normalizedY = [0] * 3
normalizedZ = [0] * 3
# The X axis is a vector from start to end
vtk.vtkMath.Subtract(endPoint, startPoint, normalizedX)
length = vtk.vtkMath.Norm(normalizedX)
vtk.vtkMath.Normalize(normalizedX)
# The Z axis is an arbitrary vector cross X
arbitrary = [0] * 3
for i in range(0, 3):
rng.Next()
arbitrary[i] = rng.GetRangeValue(-10, 10)
vtk.vtkMath.Cross(normalizedX, arbitrary, normalizedZ)
vtk.vtkMath.Normalize(normalizedZ)
# The Y axis is Z cross X
vtk.vtkMath.Cross(normalizedZ, normalizedX, normalizedY)
matrix = vtk.vtkMatrix4x4()
# Create the direction cosine matrix
matrix.Identity()
for i in range(0, 3):
matrix.SetElement(i, 0, normalizedX[i])
matrix.SetElement(i, 1, normalizedY[i])
matrix.SetElement(i, 2, normalizedZ[i])
# Apply the transforms
transform = vtk.vtkTransform()
transform.Translate(startPoint) # translate to starting point
transform.Concatenate(matrix) # apply direction cosines
transform.RotateZ(-90.0) # align cylinder to x axis
transform.Scale(1.0, length, 1.0) # scale along the height vector
transform.Translate(0, .5, 0) # translate to start of cylinder
# Transform the polydata
transformPD = vtk.vtkTransformPolyDataFilter()
transformPD.SetTransform(transform)
transformPD.SetInputConnection(cylinderSource.GetOutputPort())
cylinderSource.Update()
# Create a mapper and actor for the arrow
mapper = vtk.vtkPolyDataMapper()
actor = vtk.vtkActor()
if USER_MATRIX:
mapper.SetInputConnection(cylinderSource.GetOutputPort())
actor.SetUserMatrix(transform.GetMatrix())
else:
mapper.SetInputConnection(transformPD.GetOutputPort())
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d(my_color))
actor.GetProperty().SetOpacity(opacity)
return actor, transformPD
Now i want to ray cast a line with this oriented cylinder. unfortunately, using the vtkCylinderSource as the dataset for vtkOBBTree produces the wrong points as the result. how can i use ray-casting with a PolyDataFilter?
I came up with a solution where i export my oriented-cylinder to a .stl file and then read it again to implement the ray-casting algorithm using IntersectWithLine. The problem is i have thousands of these oriented-cylinders and this method (exporting and reading) makes my code extremely slow.
def ray_cast(filename, p_source, p_target):
'''
:param filename: STL file to perform ray casting on.
:param p_source: first point
:param p_target: second point
:return: code --> 0 : No intersection.
:return: code --> +1 : p_source lies OUTSIDE the closed surface.
:return; code --> -1 : p_source lies INSIDE closed surface
'''
reader = vtk.vtkSTLReader()
reader.SetFileName(filename)
reader.Update()
mesh = reader.GetOutput()
obbtree = vtk.vtkOBBTree()
obbtree.SetDataSet(mesh)
obbtree.BuildLocator()
pointsVTKIntersection = vtk.vtkPoints()
code = obbtree.IntersectWithLine(p_source, p_target, pointsVTKIntersection, None)
# Extracting data
pointsVTKIntersectionData = pointsVTKIntersection.GetData()
noPointsVTKIntersection = pointsVTKIntersectionData.GetNumberOfTuples()
pointsIntersection = []
for idx in range(noPointsVTKIntersection):
_tup = pointsVTKIntersectionData.GetTuple3(idx)
pointsIntersection.append(_tup)
return code, pointsIntersection, noPointsVTKIntersection
Below image shows the desired result using export-stl method. (the green spheres are intersection points)
I would appreciate any suggestion and help..
With vedo:
from vedo import *
cyl = Cylinder() # vtkActor
cyl.alpha(0.5).pos(3,3,3).orientation([2,1,1])
p1, p2 = (0,0,0), (4,4,5)
ipts_coords = cyl.intersectWithLine(p1, p2)
print('hit coords are', ipts_coords)
pts = Points(ipts_coords, r=10).color("yellow")
# print(pts.polydata()) # is the vtkPolyData object
origin = Point()
ln = Line(p1,p2)
show(origin, cyl, ln, pts, axes=True)

"Connect" actors by removing overlap or "filling" the gab. Collision detection

I am attempting to visualize a structural frame, with a hollow cylindrical cross-section (later on I would like to generalize the cross-section, but for now this will do), using VTK version 8.1.0 and Python. Disclaimer, I am quite new to both VTK and Python programming.
The problem is I am getting overlapping actors, which is obvious, to be honest. As illustrated in
Figure 1.
I would like to get rid of this, and actually "connect" the adjacent actors. I am thinking that first of all, I need some form of collision detection. Then I have considered extrapolating one of the actors inside the other, and then "cut" the remaining by use of a plane which I hopefully can create from the intersection between the actors, as explained in this example: intersection between actors.
I also thought that it might be possible to actually "extrude to a surface", but I do not know if this is something that is doable in VTK, but I am looking through as many examples as I can find.
So first of all, are any of the ideas I thought of realistic? If the extrude thing is doable, can anyone give me a hint of how to get started?
If none of the ideas are usable, does anyone of you have another idea of how to accomplish this?
I have created a minimal code snippet which shows the problem I would like to fix:
import vtk
def main():
colors = vtk.vtkNamedColors()
disk = vtk.vtkDiskSource()
disk.SetCircumferentialResolution(128)
disk.SetRadialResolution(1)
disk.SetOuterRadius(2)
disk.SetInnerRadius(2 - 0.1)
extrude_disk = vtk.vtkLinearExtrusionFilter()
extrude_disk.SetInputConnection(disk.GetOutputPort())
extrude_disk.SetExtrusionTypeToNormalExtrusion()
extrude_disk.SetVector(0, 0, 1)
extrude_disk.SetScaleFactor(1)
extrude_disk.Update()
disk2 = vtk.vtkDiskSource()
disk2.SetCircumferentialResolution(128)
disk2.SetRadialResolution(1)
disk2.SetOuterRadius(1.5)
disk2.SetInnerRadius(1.5 - 0.1)
extrude_disk2 = vtk.vtkLinearExtrusionFilter()
extrude_disk2.SetInputConnection(disk2.GetOutputPort())
extrude_disk2.SetExtrusionTypeToNormalExtrusion()
extrude_disk2.SetVector(0, 0, 1)
extrude_disk2.SetScaleFactor(1)
extrude_disk2.Update()
start_point = [0] * 3
start_point2 = [-10, 0, 0]
end_point = [0, 0, 10]
end_point2 = [0, 0, 7]
# Computing a basis
normalized_x = [0] * 3
normalized_x2 = [0] * 3
normalized_y = [0] * 3
normalized_y2 = [0] * 3
normalized_z = [0] * 3
normalized_z2 = [0] * 3
# The X axis is a vector from start to end
vtk.vtkMath.Subtract(end_point, start_point, normalized_x)
vtk.vtkMath.Subtract(end_point2, start_point2, normalized_x2)
length = vtk.vtkMath.Norm(normalized_x)
length2 = vtk.vtkMath.Norm(normalized_x2)
vtk.vtkMath.Normalize(normalized_x)
vtk.vtkMath.Normalize(normalized_x2)
# The Z axis is an arbitrary vector cross X
rng = vtk.vtkMinimalStandardRandomSequence()
rng.SetSeed(8775070) # For testing.
arbitrary = [0] * 3
for i in range(0, 3):
rng.Next()
arbitrary[i] = rng.GetRangeValue(-10, 10)
vtk.vtkMath.Cross(normalized_x, arbitrary, normalized_z)
vtk.vtkMath.Cross(normalized_x2, arbitrary, normalized_z2)
vtk.vtkMath.Normalize(normalized_z)
vtk.vtkMath.Normalize(normalized_z2)
# The Y axis is Z cross X
vtk.vtkMath.Cross(normalized_z, normalized_x, normalized_y)
vtk.vtkMath.Cross(normalized_z2, normalized_x2, normalized_y2)
matrix = vtk.vtkMatrix4x4()
matrix2 = vtk.vtkMatrix4x4()
# Create the direction cosine matrix
matrix.Identity()
matrix2.Identity()
for i in range(3):
matrix.SetElement(i, 0, normalized_x[i])
matrix2.SetElement(i, 0, normalized_x2[i])
matrix.SetElement(i, 1, normalized_y[i])
matrix2.SetElement(i, 1, normalized_y2[i])
matrix.SetElement(i, 2, normalized_z[i])
matrix2.SetElement(i, 2, normalized_z2[i])
# Apply the transforms
transform = vtk.vtkTransform()
transform.Translate(start_point) # translate to starting point
transform.Concatenate(matrix) # apply direction cosines
transform.RotateY(90.0) # align cylinder
transform.Scale(1.0, 1.0, length) # scale along the height vector
transform2 = vtk.vtkTransform()
transform2.Translate(start_point2) # translate to starting point
transform2.Concatenate(matrix2) # apply direction cosines
transform2.RotateY(90.0) # align cylinder
transform2.Scale(1.0, 1.0, length2) # scale along the height vector
# Create a mapper and actor for the disks
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(extrude_disk.GetOutputPort())
actor = vtk.vtkActor()
actor.SetUserMatrix(transform.GetMatrix())
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d("yellow"))
mapper2 = vtk.vtkPolyDataMapper()
mapper2.SetInputConnection(extrude_disk2.GetOutputPort())
actor2 = vtk.vtkActor()
actor2.SetUserMatrix(transform2.GetMatrix())
actor2.SetMapper(mapper2)
actor2.GetProperty().SetColor(colors.GetColor3d("yellow"))
# Create a renderer, render window, and interactor
renderer = vtk.vtkRenderer()
render_window = vtk.vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetWindowName("Overlapping cylinders example")
render_window_interactor = vtk.vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)
# Add the actors to the scene
renderer.AddActor(actor)
renderer.AddActor(actor2)
renderer.SetBackground(colors.GetColor3d("BkgColor"))
# Render and interact
render_window.Render()
render_window_interactor.Start()
if __name__ == '__main__':
main()
This is the end result I am looking for! just without the actors intersecting/crossing/colliding, I do not know the correct term for the behavior i apologise, as they are doing in the example I have here.
Let me start with a small clarification. Actors in VTK are simple container objects that combine geometric data and visualization properties for rendering. VTK does not offer much functionality on the level of actors. If you want to detect colliding objects, you need to solve this for the geometries (vtkPolyData).
There is no generic collision-detection- or body-intersection-engine in VTK. What you can do in VTK is to apply boolean operations. However, this is not easy to achieve robustly. There are two main approaches:
Method A: Boolean operations on the mesh
Use [vtkBooleanOperationPolyDataFilter][1] to apply boolean operations directly on the mesh. See here for an example. Unfortunately, this will fail in your case because of the mesh properties of your surfaces (hit key W when looking at the surface in the RenderWindow to inspect the wireframe of your mesh). vtkBooleanOperationPolyDataFilter will perform best if the mesh's triangles are small and have decent condition numbers, that is, if the triangles are not too spiky. (See the manual of the Verdict toolbox for some triangle metrics.) The mesh of your extruded disks, however, consists of very long, spiky triangles. What you would need to do is to first remesh the surface. VTK does not offer remeshing facilities out of the box, but related toolboxes such as VMTK do.
Getting approach (A) right for generic geometries is tricky, because you may end up with non-manifold or leaky surfaces. Also, vtkBooleanOperationPolyDataFilter is known to have some bugs (see here or here). Let's hope that these problems will be fixed some day.
Method B: Boolean operations on implicit functions
A second approach is to work with implicit functions. For instance, you can represent your tubes as implicit cylinders, and intersect these using [vtkImplicitBoolean][7]. See here for an example. The problem with this approach is that you need to convert the implicit representation of the objects to yield the resulting surface mesh. The marching cubes algorithm in VTK is rather slow, so you need to wait a long time for high resolutions. And it is impossible to preserve sharp edges. On the other hand, it is more robust and easier to deal with.
Sample code
The code below demonstrates both cases. The remeshing feature I cannot share with you here, therefore only the implicit boolean is functional. The screenshot shows, how the result looks like. (Yellow: the input surfaces, Red: the result)
For more details on terminology and alternative problem formulations, refer to "Collision detection between geometric models: a survey" by Ming and Gottschalk, 1999.
Boolean operation on implicit functions
# This code has been written by normanius under the CC BY-SA 4.0 license.
# License: https://creativecommons.org/licenses/by-sa/4.0/
# Author: https://stackoverflow.com/users/3388962/normanius
# Date: July 2018
import vtk
import numpy as np
def compute_transform(start, end):
# Better compute the matrix in numpy!
normalized_x = [0]*3
normalized_y = [0]*3
normalized_z = [0]*3
# The X axis is a vector from start to end
vtk.vtkMath.Subtract(end, start, normalized_x)
length = vtk.vtkMath.Norm(normalized_x)
vtk.vtkMath.Normalize(normalized_x)
# The Z axis is an arbitrary vector cross X
rng = vtk.vtkMinimalStandardRandomSequence()
rng.SetSeed(8775070) # For testing.
arbitrary = [0]*3
for i in range(0, 3):
rng.Next()
arbitrary[i] = rng.GetRangeValue(-10, 10)
vtk.vtkMath.Cross(normalized_x, arbitrary, normalized_z)
vtk.vtkMath.Normalize(normalized_z)
# The Y axis is Z cross X
vtk.vtkMath.Cross(normalized_z, normalized_x, normalized_y)
matrix = vtk.vtkMatrix4x4()
# Create the direction cosine matrix
matrix.Identity()
for i in range(3):
matrix.SetElement(i, 0, normalized_x[i])
matrix.SetElement(i, 1, normalized_y[i])
matrix.SetElement(i, 2, normalized_z[i])
transform = vtk.vtkTransform()
transform.Translate(start) # translate to starting point
transform.Concatenate(matrix) # apply direction cosines
transform.RotateY(90.0) # align cylinder
# Don't scale! This changes mesh properties (e.g. aspect ratio)
#transform.Scale(1.0, 1.0, length) # scale along the height vector
return transform
def transform_item(item, transform):
transformed = vtk.vtkTransformPolyDataFilter()
transformed.SetInputConnection(item.GetOutputPort())
transformed.SetTransform(transform)
transformed.Update()
return transformed
def create_pipe(radius, thickness, height):
# This type of pipe is not suited for remeshing, because remeshing does not
# preserve (feature-) edges. See create_pipe2
assert(radius>thickness)
disk = vtk.vtkDiskSource()
disk.SetCircumferentialResolution(128)
disk.SetRadialResolution(1)
disk.SetOuterRadius(radius)
disk.SetInnerRadius(radius - thickness)
pipe = vtk.vtkLinearExtrusionFilter()
pipe.SetInputConnection(disk.GetOutputPort())
pipe.SetExtrusionTypeToNormalExtrusion()
pipe.SetVector(0, 0, 1)
pipe.SetScaleFactor(height)
pipe.Update()
return pipe
def create_pipe_implicit(radius, thickness, height):
center = np.array([0,0,0])
axis = np.array([0,0,1])
centerTop = center + height*axis
centerBottom = center
# Outer cylinder.
outer = vtk.vtkCylinder()
outer.SetCenter(center)
outer.SetAxis(axis)
outer.SetRadius(radius)
# Inner cylinder.
inner = vtk.vtkCylinder()
inner.SetCenter(center)
inner.SetAxis(axis)
inner.SetRadius(radius-thickness)
# Top face.
plane1 = vtk.vtkPlane()
plane1.SetOrigin(centerTop)
plane1.SetNormal(np.array(outer.GetAxis()))
# Bottom face.
plane2 = vtk.vtkPlane()
plane2.SetOrigin(centerBottom)
plane2.SetNormal(-np.array(outer.GetAxis()))
# Put things together.
difference = vtk.vtkImplicitBoolean()
difference.AddFunction(outer)
difference.AddFunction(inner)
difference.SetOperationTypeToDifference()
intersection = vtk.vtkImplicitBoolean()
intersection.AddFunction(difference)
intersection.AddFunction(plane1)
intersection.AddFunction(plane2)
intersection.SetOperationTypeToIntersection()
pipe = intersection
# Also return inner and outer cylinder.
intersection = vtk.vtkImplicitBoolean()
intersection.AddFunction(inner)
intersection.AddFunction(plane1)
intersection.AddFunction(plane2)
intersection.SetOperationTypeToIntersection()
inner = intersection
intersection = vtk.vtkImplicitBoolean()
intersection.AddFunction(outer)
intersection.AddFunction(plane1)
intersection.AddFunction(plane2)
intersection.SetOperationTypeToIntersection()
outer = intersection
return pipe, inner, outer
def add_to_renderer(renderer, item, color, opacity=1., translate=None):
colors = vtk.vtkNamedColors()
mapper = vtk.vtkPolyDataMapper()
mapper.SetScalarVisibility(False)
mapper.SetInputConnection(item.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d(color))
actor.GetProperty().SetOpacity(opacity)
if translate:
trafo = vtk.vtkTransform()
trafo.Translate(translate)
actor.SetUserTransform(trafo)
renderer.AddActor(actor)
return mapper, actor
def evaluate_implicit(implicit_function, resolution, bounds):
sampled = vtk.vtkSampleFunction()
sampled.SetSampleDimensions(resolution, resolution, resolution)
sampled.SetModelBounds(bounds)
sampled.SetImplicitFunction(implicit_function)
iso = vtk.vtkMarchingCubes()
iso.SetValue(0,0.)
iso.SetInputConnection(sampled.GetOutputPort())
iso.Update()
return iso
def main():
colors = vtk.vtkNamedColors()
# Params.
radius = 2.
thickness = 0.5
start_point = np.array([0] * 3)
end_point = np.array([0, 0, 10])
length = np.linalg.norm(start_point-end_point)
radius2 = 2.
thickness2 = 0.5
start_point2 = np.array([-10, 0, 0])
end_point2 = np.array([0, 0, 7])
length2 = np.linalg.norm(start_point2-end_point2)
# Compute transforms.
transform = compute_transform(start_point, end_point)
transform2 = compute_transform(start_point2, end_point2)
############################################################################
# BOOLEAN OPERATIONS ON MESHES
############################################################################
if False:
pipe, inner, outer = create_pipe2(radius=radius, thickness=thickness, height=length)
pipe2, inner2, outer2 = create_pipe2(radius=radius2, thickness=thickness2, height=length2)
# Apply the transforms.
pipe = transform_item(pipe, transform)
inner = transform_item(inner, transform)
outer = transform_item(outer, transform)
pipe2 = transform_item(pipe2, transform2)
inner2 = transform_item(inner2, transform2)
outer2 = transform_item(outer2, transform2)
#pipe_2m1 = boolean_combine(pipe2, pipe, 'difference')
pipe_2m1 = boolean_combine(pipe2, pipe, 'union') # Ugly! There is a bug in vtk!
result_bool = pipe_2m1
#result_bool = boolean_combine(pipe, pipe_2m1, 'union')
#result_bool = remeshSurface(result_bool, targetArea=.1, iterations=10)
# Add items to renderer.
renderer = vtk.vtkRenderer()
opacity=1.0
#add_to_renderer(renderer=renderer, item=pipe, color='yellow', opacity=opacity)
#add_to_renderer(renderer=renderer, item=pipe2, color='yellow', opacity=opacity)
add_to_renderer(renderer=renderer, item=result_bool, color='red')
############################################################################
# IMPLICIT BOOLEAN
############################################################################
else:
# We need to know the domain where the implicit function will be
# evaulated. There is certainly other ways to achieve this. Here,
# we simply get the bounds from the meshes. Also, we add a margin
# to avoid artifacts close to the domain boundary.
pipe = create_pipe(radius=radius, thickness=thickness, height=length)
pipe2 = create_pipe(radius=radius2, thickness=thickness2, height=length2)
pipe = transform_item(pipe, transform)
pipe2 = transform_item(pipe2, transform2)
bounds = pipe.GetOutput().GetBounds()
bounds2 = pipe2.GetOutput().GetBounds()
def applyMargin(bounds, margin):
extent = [ bounds[1]-bounds[0],
bounds[3]-bounds[2],
bounds[5]-bounds[4] ]
bounds = [ bounds[0]-extent[0]*margin, bounds[1]+extent[0]*margin,
bounds[2]-extent[1]*margin, bounds[3]+extent[1]*margin,
bounds[4]-extent[2]*margin, bounds[5]+extent[2]*margin ]
return bounds
bounds = applyMargin(bounds, margin=0.1)
bounds2 = applyMargin(bounds2, margin=0.1)
# The bounds of the combined object pipe+pipe2
boundsCombo = [min(bounds[0], bounds2[0]),
max(bounds[1], bounds2[1]),
min(bounds[2], bounds2[2]),
max(bounds[3], bounds2[3]),
min(bounds[4], bounds2[4]),
max(bounds[5], bounds2[5])]
# Let's create implicit functions for the pipes.
pipeImp, innerImp, outerImp = create_pipe_implicit(radius=radius, thickness=thickness, height=length)
pipeImp2, innerImp2, outerImp2 = create_pipe_implicit(radius=radius2, thickness=thickness2, height=length2)
pipeImp.SetTransform(transform.GetInverse())
pipeImp2.SetTransform(transform2.GetInverse())
innerImp.SetTransform(transform.GetInverse())
innerImp2.SetTransform(transform2.GetInverse())
outerImp.SetTransform(transform.GetInverse())
outerImp2.SetTransform(transform2.GetInverse())
# Apply the intersection.
difference = vtk.vtkImplicitBoolean()
difference.AddFunction(pipeImp2)
difference.AddFunction(outerImp)
difference.SetOperationTypeToDifference()
union = vtk.vtkImplicitBoolean()
union.AddFunction(difference)
union.AddFunction(pipeImp)
union.SetOperationTypeToUnion()
# This last operation is required to "cut through" the first pipe.
difference = vtk.vtkImplicitBoolean()
difference.AddFunction(union)
difference.AddFunction(innerImp2)
difference.SetOperationTypeToDifference()
# Convert the implicit functions into surfaces.
pipe = evaluate_implicit(implicit_function=pipeImp,
resolution=100,
bounds=bounds)
pipe2 = evaluate_implicit(implicit_function=pipeImp2,
resolution=100,
bounds=bounds2)
result = evaluate_implicit(implicit_function=difference,
resolution=100,
bounds=boundsCombo)
# Add items to renderer.
renderer = vtk.vtkRenderer()
opacity=1.
add_to_renderer(renderer=renderer, item=pipe, color='yellow', opacity=opacity, translate=[0,5,0])
add_to_renderer(renderer=renderer, item=pipe2, color='yellow', opacity=opacity, translate=[0,5,0])
add_to_renderer(renderer=renderer, item=result, color='red')
# Create a renderer, render window, and interactor.
render_window = vtk.vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetWindowName("Overlapping cylinders example")
render_window.SetSize(1000,1000)
render_window_interactor = vtk.vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)
# Add the actors to the scene.
renderer.SetBackground(colors.GetColor3d("Gray"))
# Render and interact.
render_window.Render()
render_window_interactor.Start()
if __name__ == '__main__':
main()
Boolean operation on meshes
# This code has been written by normanius under the CC BY-SA 4.0 license.
# License: https://creativecommons.org/licenses/by-sa/4.0/
# Author: https://stackoverflow.com/users/3388962/normanius
# Date: July 2018
import vtk
import numpy as np
try:
# Remesher based on VMTK. Sorry, cannot share this with you.
from geometry.remesher import remeshSurface
from vtkutils.misc import extractEdges
from geometry.capper import capSurface
except:
remeshSurface = None
extractEdges = None
capSurface = None
def compute_transform(start, end):
# Better compute the matrix in numpy!
normalized_x = [0]*3
normalized_y = [0]*3
normalized_z = [0]*3
# The X axis is a vector from start to end
vtk.vtkMath.Subtract(end, start, normalized_x)
length = vtk.vtkMath.Norm(normalized_x)
vtk.vtkMath.Normalize(normalized_x)
# The Z axis is an arbitrary vector cross X
rng = vtk.vtkMinimalStandardRandomSequence()
rng.SetSeed(8775070) # For testing.
arbitrary = [0]*3
for i in range(0, 3):
rng.Next()
arbitrary[i] = rng.GetRangeValue(-10, 10)
vtk.vtkMath.Cross(normalized_x, arbitrary, normalized_z)
vtk.vtkMath.Normalize(normalized_z)
# The Y axis is Z cross X
vtk.vtkMath.Cross(normalized_z, normalized_x, normalized_y)
matrix = vtk.vtkMatrix4x4()
# Create the direction cosine matrix
matrix.Identity()
for i in range(3):
matrix.SetElement(i, 0, normalized_x[i])
matrix.SetElement(i, 1, normalized_y[i])
matrix.SetElement(i, 2, normalized_z[i])
transform = vtk.vtkTransform()
transform.Translate(start) # translate to starting point
transform.Concatenate(matrix) # apply direction cosines
transform.RotateY(90.0) # align cylinder
# Don't scale! This changes mesh properties (e.g. aspect ratio)
#transform.Scale(1.0, 1.0, length) # scale along the height vector
return transform
def transform_item(item, transform):
transformed = vtk.vtkTransformPolyDataFilter()
transformed.SetInputConnection(item.GetOutputPort())
transformed.SetTransform(transform)
transformed.Update()
return transformed
def create_pipe(radius, thickness, height):
# This type of pipe is not suited for remeshing, because remeshing does not
# preserve (feature-) edges. See create_pipe2
assert(radius>thickness)
disk = vtk.vtkDiskSource()
disk.SetCircumferentialResolution(128)
disk.SetRadialResolution(1)
disk.SetOuterRadius(radius)
disk.SetInnerRadius(radius - thickness)
pipe = vtk.vtkLinearExtrusionFilter()
pipe.SetInputConnection(disk.GetOutputPort())
pipe.SetExtrusionTypeToNormalExtrusion()
pipe.SetVector(0, 0, 1)
pipe.SetScaleFactor(height)
pipe.Update()
return pipe
def create_pipe2(radius, thickness, height):
# Create pipes with decently meshed surfaces, if remeshSurface is
# availaable.
# Align the cylinder in the same way as create_pipe() does.
transform = vtk.vtkTransform()
transform.RotateX(90.0)
transform.Translate(0,height/2,0)
outer = vtk.vtkCylinderSource()
outer.SetRadius(radius)
outer.SetResolution(128)
outer.SetHeight(height)
outer.CappingOff()
outer.Update()
outer = transform_item(outer, transform)
inner = vtk.vtkCylinderSource()
inner.SetRadius(radius-thickness)
inner.SetResolution(128)
inner.SetHeight(height)
inner.CappingOff()
inner.Update()
inner = transform_item(inner, transform)
# remeshSurface, extractEdges and capSurface are not available, sorry!
if remeshSurface:
outer = remeshSurface(outer, targetArea=.1, iterations=10, smoothing=False)
inner = remeshSurface(inner, targetArea=.1, iterations=10, smoothing=False)
# So far, we have two concentric cylinders.
# Close the upper and lower caps using vtkContourTriangulator.
result = combine_polydata(outer, inner)
edges1 = extractEdges(outer, mode='separated')
edges2 = extractEdges(inner, mode='separated')
assert(len(edges1)==len(edges2)==2)
for i in range(2):
edgesBottom = combine_polydata(edges1[i], edges2[i])
bottom = vtk.vtkContourTriangulator()
bottom.SetInputConnection(edgesBottom.GetOutputPort())
bottom.Update()
result = combine_polydata(result, bottom)
# Return also the inner and outer cylinders.
#return result, inner, outer
inner = capSurface(inner, remesh=True, returnCaps=False)
outer = capSurface(outer, remesh=True, returnCaps=False)
return result, inner, outer
def clean_mesh(source):
clean = vtk.vtkCleanPolyData()
#clean.ConvertPolysToLinesOff()
clean.SetInputData(source.GetOutput())
clean.Update()
return clean
def fill_holes(source):
fill = vtk.vtkFillHolesFilter()
fill.SetInputConnection(source.GetOutputPort())
fill.SetHoleSize(100)
fill.Update()
return fill
def combine_polydata(source1, source2):
if source2 is None:
return source1
if source1 is None:
return source2
combo = vtk.vtkAppendPolyData()
combo.AddInputData(source1.GetOutput())
combo.AddInputData(source2.GetOutput())
combo.Update()
return clean_mesh(combo)
def boolean_combine(source1, source2, method='union'):
assert(method.lower() in ['union', 'or',
'intersection', 'and',
'difference', 'subtract', 'minus'])
source1 = source1 if source1 is not None else None
source2 = source2 if source2 is not None else None
method = method.lower()
if source1 is None and source2 is None:
return None
# vtkBooleanOperationPolyDataFilter cannot handle empty sources!
if source2 is None or source2.GetOutput().GetNumberOfPoints() == 0:
return source1
if source1 is None or source1.GetOutput().GetNumberOfPoints() == 0:
return source2
boolean = vtk.vtkBooleanOperationPolyDataFilter()
if method in ['union', 'or']:
boolean.SetOperationToUnion()
elif method in ['intersection', 'and']:
boolean.SetOperationToIntersection()
elif method in ['difference', 'subtract', 'minus']:
boolean.SetOperationToDifference()
boolean.SetInputData(0, source1.GetOutput())
boolean.SetInputData(1, source2.GetOutput())
boolean.Update()
result = boolean
if result.GetOutput().GetNumberOfPoints() == 0:
# No intersection betweeen source1 and source2.
if method in ['union', 'or']:
result = combine_polydata(source1, source2)
elif method in ['intersection', 'and']:
result = vtk.vtkPolyData()
elif method in ['difference', 'subtract', 'minus']:
result = vtk.vtkPolyData()
result.DeepCopy(source1.GetOutput())
pt = vtk.vtkPassThroughFilter()
pt.SetInputData(result)
pt.Update()
result = pt
return clean_mesh(result)
def add_to_renderer(renderer, item, color, opacity=1., translate=None):
colors = vtk.vtkNamedColors()
mapper = vtk.vtkPolyDataMapper()
mapper.SetScalarVisibility(False)
mapper.SetInputConnection(item.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d(color))
actor.GetProperty().SetOpacity(opacity)
if translate:
trafo = vtk.vtkTransform()
trafo.Translate(translate)
actor.SetUserTransform(trafo)
renderer.AddActor(actor)
return mapper, actor
def main():
colors = vtk.vtkNamedColors()
# Params.
radius = 2.
thickness = 0.5
start_point = np.array([0] * 3)
end_point = np.array([0, 0, 10])
length = np.linalg.norm(start_point-end_point)
radius2 = 2.
thickness2 = 0.5
start_point2 = np.array([-10, 0, 0])
end_point2 = np.array([0, 0, 7])
length2 = np.linalg.norm(start_point2-end_point2)
# Compute transforms.
transform = compute_transform(start_point, end_point)
transform2 = compute_transform(start_point2, end_point2)
############################################################################
# BOOLEAN OPERATIONS ON MESHES
############################################################################
if remeshSurface and False:
pipe, inner, outer = create_pipe2(radius=radius,
thickness=thickness,
height=length)
pipe2, inner2, outer2 = create_pipe2(radius=radius2,
thickness=thickness2,
height=length2)
# Apply the transforms.
pipe = transform_item(pipe, transform)
inner = transform_item(inner, transform)
outer = transform_item(outer, transform)
pipe2 = transform_item(pipe2, transform2)
inner2 = transform_item(inner2, transform2)
outer2 = transform_item(outer2, transform2)
# Ugly! There is a bug in vtk!
result_bool = boolean_combine(pipe2, pipe, 'union')
else:
pipe = create_pipe(radius=radius, thickness=thickness, height=length)
pipe2 = create_pipe(radius=radius2, thickness=thickness2, height=length2)
pipe = transform_item(pipe, transform)
pipe2 = transform_item(pipe2, transform2)
# A warning is printed: "No Intersection between objects"...
# This has something to do with the mesh properties.
result_bool = boolean_combine(pipe2, pipe, 'difference')
# Add items to renderer.
renderer = vtk.vtkRenderer()
opacity=1.0
add_to_renderer(renderer=renderer, item=pipe, color='yellow', opacity=opacity, translate=[0,5,0])
add_to_renderer(renderer=renderer, item=pipe2, color='yellow', opacity=opacity, translate=[0,5,0])
add_to_renderer(renderer=renderer, item=result_bool, color='red')
# Create a renderer, render window, and interactor.
render_window = vtk.vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetWindowName("Overlapping cylinders example")
render_window.SetSize(1000,1000)
render_window_interactor = vtk.vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)
# Add the actors to the scene.
renderer.SetBackground(colors.GetColor3d("Gray"))
# Render and interact.
render_window.Render()
render_window_interactor.Start()
if __name__ == '__main__':
main()

How to plot 3D ellipsoid with Mayavi

I would like to plot diffusion tensors(ellipsoid) in diffusion MRI. The data have three Eigenvalues of the corresponding diffusion tensor. I want to draw an 3D Ellipsoid with its semi-axes lengths corresponding to those three Eigenvalues.
How to do it with Mayavi?
Google brought me here and to the answer. I found how to render an ellipsoid here: https://github.com/spyke/spyke/blob/master/demo/mayavi_test.py and combined it with the arrow from here https://stackoverflow.com/a/20109619/2389450 to produce something like: http://imageshack.com/a/img673/7664/YzbTHY.png
Cheers,
Max
Code:
from mayavi.api import Engine
from mayavi.sources.api import ParametricSurface
from mayavi.modules.api import Surface
from mayavi import mlab
from tvtk.tools import visual
import numpy as np
def Arrow_From_A_to_B(x1, y1, z1, x2, y2, z2,scale=None):
ar1=visual.arrow(x=x1, y=y1, z=z1)
ar1.length_cone=0.4
arrow_length=np.sqrt((x2-x1)**2+(y2-y1)**2+(z2-z1)**2)
if scale is None:
ar1.actor.scale=[arrow_length, arrow_length, arrow_length]
else:
ar1.actor.scale=scale
ar1.pos = ar1.pos/arrow_length
ar1.axis = [x2-x1, y2-y1, z2-z1]
return ar1
engine = Engine()
engine.start()
scene = engine.new_scene()
scene.scene.disable_render = True # for speed
visual.set_viewer(scene)
surfaces = []
for i in range(2):
source = ParametricSurface()
source.function = 'ellipsoid'
engine.add_source(source)
surface = Surface()
source.add_module(surface)
actor = surface.actor # mayavi actor, actor.actor is tvtk actor
#actor.property.ambient = 1 # defaults to 0 for some reason, ah don't need it, turn off scalar visibility instead
actor.property.opacity = 0.7
actor.property.color = (0,0,1) # tuple(np.random.rand(3))
actor.mapper.scalar_visibility = False # don't colour ellipses by their scalar indices into colour map
actor.property.backface_culling = True # gets rid of weird rendering artifact when opacity is < 1
actor.property.specular = 0.1
#actor.property.frontface_culling = True
actor.actor.orientation = np.array([1,0,0]) * 360 # in degrees
actor.actor.origin = np.array([0,0,0])
actor.actor.position = np.array([0,0,0])
actor.actor.scale = np.array([ 0.26490647, 0.26490647, 0.92717265])
actor.enable_texture=True
actor.property.representation = ['wireframe', 'surface'][i]
surfaces.append(surface)
Arrow_From_A_to_B(0,0,0, 0.26490647, 0, 0,np.array([0.26490647,0.4,0.4]))
Arrow_From_A_to_B(0,0,0, 0, 0.26490647, 0,np.array([0.4,0.26490647,0.4]))
Arrow_From_A_to_B(0,0,0, 0, 0, 0.92717265,np.array([0.4,0.4,0.92717265]))
source.scene.background = (1.0,1.0,1.0)
scene.scene.disable_render = False # now turn it on
# set the scalars, this has to be done some indeterminate amount of time
# after each surface is created, otherwise the scalars get overwritten
# later by their default of 1.0
for i, surface in enumerate(surfaces):
vtk_srcs = mlab.pipeline.get_vtk_src(surface)
print('len(vtk_srcs) = %d' % len(vtk_srcs))
vtk_src = vtk_srcs[0]
try: npoints = len(vtk_src.point_data.scalars)
except TypeError:
print('hit the TypeError on surface i=%d' % i)
npoints = 2500
vtk_src.point_data.scalars = np.tile(i, npoints)
# on pick, find the ellipsoid with origin closest to the picked coord,
# then check if that coord falls within that nearest ellipsoid, and if
# so, print out the ellispoid id, or pop it up in a tooltip
mlab.show()

Categories

Resources