I am trying to find intersection points between 3d object .stl file (that I imported to python using numpy-stl) and 3d plot(that I generated using matplotlib). The 3d object is a cad drawing and 3d plot is a bunch of curves in 3d. I can plot them on the same figure but I have no idea how I can find intersection points between the trajectory and the drawing. My idea was to convert the curve into .stl using save() function from the numpy-stl module after plotting it and show them together on a cad application like freeCAD together and find intersection using the application's functionality. But it does not work as simple because the plots are point based and .stl is triangle based. If anyone has any advice on how to approach this problem, please let me know!!
Here is the code to plot both .stl object and the 3d plot. This is what I have so far.
#allInitialE is 1D list, allX, allY, allZ are all 2D lists
from stl import mesh
from numpy import *
from mpl_toolkits import mplot3d
from matplotlib import pyplot as plt
fig = plt.figure()
ax = plt.axes(projection = '3d')
your_mesh = mesh.Mesh.from_file('fileName.stl')
your_mesh.translate([0,7,0])
ax.add_collection3d(mplot3d.art3d.Poly3DCollection(your_mesh.vectors))
ax.view_init(azim = -90, elev = 0)
maxE = max(allInitialE)
ax.set_xlabel('x axis (m)') # y and z are flipped to make it easier for me to visualize
ax.set_ylabel('z axix (m)')
ax.set_zlabel('y axix (m)')
plt.title('Particle Trajectory')
for k in range(numParticles): #iterate through each of the particles' xyz data
e = allInitialE[k]
if e < maxE/3:
ax.plot3D(allX[k], allZ[k], allY[k], 'g-')
elif e < maxE/2:
ax.plot3D(allX[k], allZ[k], allY[k], 'b-')
else:
ax.plot3D(allX[k], allZ[k], allY[k], 'r-')
plt.show()
1 idea: Is there any way to convert .stl object to a set of plane functions? If so, I could make the plots into lines and find intersection between the plane and the line?
2nd idea: Or, since .stl are vector based, I can use vector calculation? i.e. see if a vector on a curve (line segment) has a common point as a triangle on a .stl object (triangle is defined by three vectors).
Please give me any idea you may have! Thank you so much.
Another way might be to use VTK. You can convert your numpy-stl object (obj) to a vtkPolyData
import vedo
import vtk
import itertools
obj = your_mesh
verts = list(itertools.chain(*(obj.vectors)))
faces = [[i*3, i*3+1, i*3+2] for i in range(len(verts)//3)]
vpoly = vedo.Mesh([verts, faces]).clean().polydata()
Then you could use vtkCutter https://discourse.vtk.org/t/get-intersection-of-polydata-line-and-a-plane/3894/3 or vtkIntersectionPolyDataFilter VTK check polydata point objects for intersection
Concerning your 1st idea, yes, you can convert your STL file into a set of triangles, just check out:
your_mesh.vectors
which is an array of triangle 3D vertices. From those you can construct a plane, and then calculate intersection between segment and plane.
Concerning the 2nd idea, you could do that only if your trajectory crosses exactly to a vertex, otherwise it would not detect the intersection. (Or you would need to give it a margin).
I found a way using pyoctree, which has a function to find intersection between line segments and mesh. Here is the link: https://pypi.org/project/pyoctree/
I was able to use rayIntersection() to do what I wanted to do, really quick. This Intersection between line and triangle in 3D also helped, but the calculation was very slow when it dealt with 60K+ points in a curve.
Related
I am trying to triangulate an annulus using the scipy.spatial.Delaunay() function, but cannot get the desired result. Here is my code:
from scipy.spatial import Delaunay
NTheta = 26
NR = 8
a0 = 1.0
#define base rectangle (r,theta) = (u,v)
u=np.linspace(0, 2*np.pi, NTheta)
v=np.linspace(1*a0, 3*a0, NR)
u,v=np.meshgrid(u,v)
u=u.flatten()
v=v.flatten()
#evaluate the parameterization at the flattened u and v
x=v*np.cos(u)
y=v*np.sin(u)
#define 2D points, as input data for the Delaunay triangulation of U
points2D=np.vstack([u,v]).T
xy0 = np.vstack([x,y]).T
Tri1 = Delaunay(points2D) #triangulate the rectangle U
Tri2 = Delaunay(xy0) #triangulate the annulus
#plt.scatter(x, y)
plt.triplot(x, y, Tri1.simplices, linewidth=0.5)
plt.show()
plt.triplot(x, y, Tri2.simplices, linewidth=0.5)
plt.show()
I get the following:
The triangulation of the annulus itself clearly gives unwanted triangles. The triangulation of the base rectangle seems to give the proper result, until you realise that the annulus is not actually closed, by stretching the annulus (i.e., moving its nodes) a bit.
So, my question is, how do I get the proper triangulation that accounts for the non-trivial topology? Can I remove simplices from the triangulation of the annulus -- for example, based on the length of the bonds -- or somehow stitch the two ends of the base rectangle together? Is there a simple way of doing this?
Answer:
I accepted the answer below but it does not completely solve the question as asked. I still don't know how to tile a periodic surface using scipy.Delaunay (i.e., the qhull routine). However, using a mask as defined below, one can create a new list of triangle simplices, and that should serve for many purposes. However, one cannot use this list with the other methods defined in the scipy.Delaunay class. So, be careful!
qhull works with the convex hull. So it can't work directly with that concave interior. In fig2 it is filling the interior with triangles. That may be more obvious if we add a (0,0) point to xy0.
last_pt = xy0.shape[0]
xy1 = np.vstack((xy0,(0,0))) # add ctr point
Tri3 = Delaunay(xy1)
print(Tri3.points.shape, Tri3.simplices.shape)
plt.triplot(Tri3.points[:,0], Tri3.points[:,1], Tri3.simplices, linewidth=0.5)
plt.show()
Remove the simplices that contain that center point:
mask = ~(Tri3.simplices==last_pt).any(axis=1)
plt.triplot(Tri3.points[:,0], Tri3.points[:,1], Tri3.simplices[mask,:], linewidth=0.5)
plt.show()
To stitch the two ends together, removing a value from u seems to work:
u = u[:-1]
In a FEM model you might leave the center elements in place, but give them the appropriate 'neutral' properties (insulating or whatever works).
What I am trying to do is to create a 3D triangulated mesh that can be parsed into a .vtk or .stl file for use in 3D printing application. Right now I am stuck with the creation of the triangle mesh. The geometry I want to create are basically three dimensional sine waves that have a certain thickness and intersect each other. So far I got one sine wave. Here's a MWE:
import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage
import scipy.spatial
# create empty 3d array
array = np.zeros((100, 100, 100))
# create 3D sine wave in empty array
strut = np.sin(np.linspace(1, 10, 100))*12
for k in enumerate(strut):
y_shift = int(np.round(strut[k[0]]))
array[k, 50 + y_shift, 50] = 1
pattern = np.ones((4, 4, 4))
# convolve the array with the pattern / apply thickness
conv_array = ndimage.convolve(array, pattern)
# create list with data coordinates from convolved array
data = list()
for j in range(conv_array.shape[0]):
for k in range(conv_array.shape[1]):
for l in range(conv_array.shape[2]):
if conv_array[j, k, l] != 0:
data.append([j, k, l])
data = np.asarray(data)
tri = scipy.spatial.Delaunay(data)
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.hold(True)
ax.plot_trisurf(data[:, 0], data[:, 1], data[:, 2], triangles=tri.simplices)
plt.show()
What it does: I create an empty array which I fill with a sine wave represented by ones. I convolve that array with a rectangular array of a defined size, which gives me a thicker sine wave in space. Then the array gets converted into coordinate form so that it can be triangulated using Delaunay triangulation. What I get is this:
Plot
As you can see the triangulation kinda worked, but it fills the space between the sine wave amplitudes. Is there a way to remove the filled spaced? Or prevent it from doing them in the first place? The sine wave also looks wrong at the ends and I am not sure why. Is this even the best method to achieve want I am trying to do?
The parsing to a .vtk file should not present a problem, but I need a clean structure first. Thanks in advance for any kind of help!
I would not reinvent the wheel and do all that on my own. Rather than that, use python-vtk and paraview (which is a post-processing application for 3D data) to do the triangulation for you. "Just" create the points and do the rest in that application.
I don't know much about 3D printing, but I know my fair share about STL and VTK. It is a pain to do manually and the VTK library has has some nice Python examples and a dedicated STLWriter. You just need to wrap your head around the workflow of VTK and how it manages things internally. This is where paraview comes in quite handy. It enables you to record your actions that you do in the GUI and displays them and displays them in Python. This is great to learn the way it works internally.
Finally I got something very close to what I want. In case someone is interested in the answer:
Instead of going with the point cloud approach I dug myself into VTK (which is a pain to learn, but has a lot of functionality) with python.
My algorithm is basically this:
Approximate the sine wave as a simple triangular wave first.
Feed the x, y and z coordinates of the wave into a vtkPoints object
Use vtkParametricSpline to get a smooth wave
vtkSplineFilter to have control over the smoothness of the wave
vtkTubeFilter to create a volume from the line
vtkTriangleFilter for meshing
vtkSTLWriter
I made a triangulation object in matplotlib (out of the P matrix, which contains points coordinates, and the T matrix, which contains the triangles nodes, that describe a rectangle minus a hole) and computed some scalar field called phi at the nodes of this triangulation (using a finite element method applied to a Poisson equation). Using this triangulation I compute the gradient, which is what I am interested in. I made a quiver plot of this vector field, everything is nice, the vectors don't intersect with the hole.
However, when I want to plot streamlines via the streamplot function, they intersect the hole, like in the following figure:
The thing is, in order to trace streamlines, I have to create a structured grid for the streamplot function. But since my vector field is not defined at the nodes of the regular grid, I need to interpolate the values at these nodes. For that I used griddata. The problem is, in doing so, the hole is covered by the regular grid so the vector field becomes defined inside the hole, hence the result. Here is the relevant piece of code that produced the picture:
def plot_streamlines(P, T, phi):
triangulation = tr.Triangulation(P[:,0], P[:,1], T)
interpolator = tr.CubicTriInterpolator(triangulation, phi)
(u_x,u_y) = interpolator.gradient(triangulation.x, triangulation.y)
grid_x, grid_y = np.mgrid[x_min:x_max:100j, y_min:y_max:100j]
grid_u_x = ip.griddata(P, u_x, (grid_x,grid_y), method='cubic')
grid_u_y = ip.griddata(P, u_y, (grid_x,grid_y), method='cubic')
pl.streamplot(grid_x[:,0], grid_y[0,:], -grid_u_x.T, -grid_u_y.T)
I am aware of masked arrays but didn't manage to use the mask to get the result I wanted. I wanted to create a kind of masked regulard grid and then interpolate the vector field on it, but I didn't manage to do it. Does somebody has experience with this kind of problem? Any suggestion will be appreciated.
Thanks!
In your code sample interpolator.gradientis already an interpolator i.e. you do not need to use it in combination with griddata. Try:
grid_u_x, grid_u_y = interpolator.gradient(grid_x, grid_y)
As this interpolator is aware of your triangulation mesh, it should result is a velocity vector filled of nan outside your mesh. Streamplot should hopefully be able to handle this gracefully as in this example:
http://matplotlib.org/examples/images_contours_and_fields/streamplot_demo_masking.html
I'm using numpy and scipy to generate a density plot from 3D coordinate information. I can generate a density plot of the data successfully by generating a KDE with the following code
xyz = np.vstack([x,y,z])
kde = stats.gaussian_kde(xyz)
density = kde(xyz)
But how can I use this information to find the coordinates that associate with the 3D point of greatest density?
I've tried
max(density)
which returns a value that I can then find the index of with
density.argmax(axis=0)
but then I hit a blank as I can't seem to use that index to grab the associated coordinates from xyz and I'm unsure if this is the right approach.
From here, I can use
xyz.T[np.argmax(density)]
to return the 3D coordinates of the densest point in my data
I have a series of x,y coordinates and associated heading angles for multiple aircraft. I can plot the paths flown, and I would like to use a special marker to mark a particular location along the path that also shows the aircraft's heading when it was at that location.
Using matplotlib.pyplot I've used an arrowhead with no base to do this, but having to define the head and tail locations ended up with inconsistent arrowhead lengths when plotting multiple aircraft. I also used a custom three-sided symbol with the tuple (numsides, style, angle) as well as the wedge and bigvee symbols, but they never look very good.
From Custom arrow style for matplotlib, pyplot.annotate Saullo Castro showed a nice custom arrow (arrow1) that I'm wondering whether it can be used or converted in such a way as to just simply plot it at a given x,y and have its orientation defined by a heading angle.
I can plot the custom arrow with the following. Any ideas on how to rotate it to reflect a heading?
a1 = np.array([[0,0],[0,1],[-1,2],[3,0],[-1,-2],[0,-1],[0,0]], dtype=float)
polB = patches.Polygon(a1, closed=True, facecolor='grey')
ax.add_patch(polB)
Thanks in advance.
So I made the polygon a little simpler and also found that the rotation could be done by using mpl.transforms.Affine2D().rotate_deg_around():
a2 = np.array([[newX,newY+2],[newX+1,newY-1],[newX,newY],[newX-1,newY-1],[newX,newY+2]], dtype=float)
polB = patches.Polygon(a2, closed=True, facecolor='gold')
t2 = mpl.transforms.Affine2D().rotate_deg_around(newX,newY,heading) + newax.transData
polB.set_transform(t2)
newax.add_patch(polB)
I first tried to overlay the polygon on a line plotted from the x,y coordinates. However, the scales of the x and y axes were not equal (nor did I want them to be), so the polygon ended up looking all warped and stretched when rotated. I got around this by first adding a new axis with equal x/y scaling:
newax = fig.add_axes(ax.get_position(), frameon=False)
newax.set_xlim(-20,20)
newax.set_ylim(-20,20)
I could at least then rotate all I wanted and not have the warp issue. But then I needed to figure out how to basically connect the two axes so that I could plot the polygon on the new axis at a point referenced from the original axis. The way I figured to do this was by using transformations to go from the data coordinates on the original axis, converting them to display coordinates, and then inverting them back to data coordinates except this time at the data coordinates on the new axis:
inTrans = ax.transData.transform((x, y))
inv = newax.transData.inverted()
newTrans = inv.transform((inTrans[0], inTrans[1]))
newX = newTrans[0]
newY = newTrans[1]
It felt a little like some sort of Rube Goldberg machine to do it this way, but it did what I wanted.
In the end, I decided I didn't like this approach and went with keeping it simpler and using a fancy arrowhead instead of a polygon. Such is life...