1

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!

Diego Martínez Giardini
  • 1,111
  • 3
  • 16
  • 20

2 Answers2

2

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()

enter image description here

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()

enter image description here

Greg Whittier
  • 3,105
  • 1
  • 19
  • 14
  • Hello Greg!, That's great! But now I can't rotate it, so how can I use the code that make the rotation possible with this? – Diego Martínez Giardini Sep 19 '13 at 13:55
  • @DiegoMartínezGiardini I've modified my answer to address this. The Poly3DCollection object is more flexible. Adding the remaining faces is left as an exercise for now. You just need to create the Poly3DCollection objects by provide a list of vertices. Then you use the set_color method to... set the color. – Greg Whittier Sep 19 '13 at 20:02
0

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: enter image description here 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 enter image description here

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.

Greg
  • 11,654
  • 3
  • 44
  • 50
  • But how I draw diagonal crosses in each face, I'm stuck right know! The reason why I need to paint the faces is to identify each one, but drawing "x" to each face is a very good alternative!! – Diego Martínez Giardini Sep 18 '13 at 20:50