问题
I have done a cube that can be rotated on python but now I want to colour the faces for me to identify each face when it's rotated. The code below:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from itertools import product, combinations
from numpy import sin, cos
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect("auto")
ax.set_autoscale_on(True)
#dibujar cubo
r = [-10, 10]
for s, e in combinations(np.array(list(product(r,r,r))), 2):
if np.sum(np.abs(s-e)) == r[1]-r[0]:
ax.plot3D(*zip(s,e), color="b")
#dibujar punto
#ax.scatter([0],[0],[0],color="g",s=100)
d = [-2, 2]
theta = np.radians(45)
for s, e in combinations(np.array(list(product(d,d,d))), 2):
if np.sum(np.abs(s-e)) == d[1]-d[0]:
s_rotated = [s[0]*cos(theta)-s[1]*sin(theta),
s[0]*sin(theta)+s[1]*cos(theta),
s[2]]
e_rotated = [e[0]*cos(theta)-e[1]*sin(theta),
e[0]*sin(theta)+e[1]*cos(theta),
e[2]]
ax.plot3D(*zip(s_rotated,e_rotated), color="g")
plt.show()
So I want to paint the cube that is inside. Any help? Thank you!
回答1:
You can use patches.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from itertools import product, combinations
from numpy import sin, cos
from matplotlib.patches import Rectangle, Circle, PathPatch
import mpl_toolkits.mplot3d.art3d as art3d
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect("auto")
ax.set_autoscale_on(True)
r = [-10, 10]
for s, e in combinations(np.array(list(product(r,r,r))), 2):
if np.sum(np.abs(s-e)) == r[1]-r[0]:
ax.plot3D(*zip(s,e), color="b")
colors = ['b', 'g', 'r', 'c', 'm', 'y']
for i, (z, zdir) in enumerate(product([-2,2], ['x','y','z'])):
side = Rectangle((-2, -2), 4, 4, facecolor=colors[i])
ax.add_patch(side)
art3d.pathpatch_2d_to_3d(side, z=z, zdir=zdir)
plt.show()
If you need to rotate more generally than in the x-y plane, you can use Poly3Dcollection. This just draws the top and bottom of the cube. How you want to generate the vertices will depend on the details of what you're doing.
from mpl_toolkits.mplot3d import Axes3D, art3d
import matplotlib.pyplot as plt
import numpy as np
from itertools import product, combinations
from numpy import sin, cos
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect("auto")
ax.set_autoscale_on(True)
r = [-10, 10]
for s, e in combinations(np.array(list(product(r,r,r))), 2):
if np.sum(np.abs(s-e)) == r[1]-r[0]:
ax.plot3D(*zip(s,e), color="b")
btm = np.array([[-2, -2, -2],
[-2, 2, -2],
[ 2, 2, -2],
[2, -2,-2]])
top = np.array([[-2, -2, 2],
[-2, 2, 2],
[ 2, 2, 2],
[2, -2,2]])
theta = np.radians(45)
rot_mx = np.array([[cos(theta), sin(theta), 0],
[-sin(theta), cos(theta), 0],
[ 0, 0, 1]])
btm = np.dot(btm, rot_mx)
side = art3d.Poly3DCollection([btm])
side.set_color('r')
ax.add_collection3d(side)
top = np.dot(top, rot_mx)
side = art3d.Poly3DCollection([top])
side.set_color('g')
ax.add_collection3d(side)
plt.show()
回答2:
The effect your looking for will be difficult to achieve for the following reasons:
matplotlib
doesn't know it is a cube, so you will need to calculate the points in the plane to colour.Matplotlib 3D plotting is a projection of 3D data into 2D, this is particularly apparent when plotting the intersection of a line and a plane:
when the line is below the plane it doesn't look like it is, you can circumvent this by say setting the transparency,alpha
of the line to be less when below the line. What this means is you will need to calculated, based on the rotation which planes are "front facing" to the viewer which can change interactively!The is no way to paint something in matplotlib 3d, for 2D you can use plt.fill_between but this function does not extend to 3D.
While the first two problems can in theory be solved by writing out the mathematics of the cube, defining the planes and there visibility with respect to the viewer, the last I really don't know how you would solve. Essentially you need to write fill between, a function that fills in the area with shaded polygons in 3D.
With this depressing outlook I can suggest some alternatives, just add some diagonal crosses to each face, since you already check if the vertices are on the same edge you just need to check if they are diagonal, plot and colour accordingly.
Or move to a graphical plotting tool which is designed to do this sort of thing. If you have any experience with latex then try tikz 3D plotting it combines code with very pretty output, although it is a bit of a faf.
Edit: Here is how one would put crosses
into the cube:
The if
statement in the for loop checks the distance between the points s
and e
. Due to the way this is done it does not check the actual difference but rather how many vector lengths separate the two, this can be checked by printing np.sum(np.abs(s-e))
which returns lengths of 4
, 8
and 12
. So we want those separated by two vectors, that is we add in another if
statement
elif np.sum(np.abs(s-e))== 2 * (d[1]-d[0]):
s_rotated = [s[0]*cos(theta)-s[1]*sin(theta),
s[0]*sin(theta)+s[1]*cos(theta),
s[2]]
e_rotated = [e[0]*cos(theta)-e[1]*sin(theta),
e[0]*sin(theta)+e[1]*cos(theta),
e[2]]
ax.plot3D(*zip(s_rotated,e_rotated), color="r")
which draws all points separated by two vector lengths in red
Clearly you would like to plot each cross in a different color, this is not possible with the code in it's current form. This is because we check only the lengths between points, one needs to have a way to distinguish between different sets of points.
I'm unsure of the best way to do this and so will stop here, in my opinion you need to stop just running through all combinations of points and actually label the points properly.
来源:https://stackoverflow.com/questions/18853563/how-can-i-paint-the-faces-of-a-cube