--- title: Session. 3D Objects in Python categories: session --- # Learning Objectives **Key learning outcome** Improve the understanding of the mathematical descriptions of 3D Motion, by testing implementations in Python. **Secondary outcomes** if you have time, it is worth browsing different libraries and frameworks to build, visualise, and animate scenes using 3D objects. # Briefing ## Recap: a simple object Remember, last week we worked with this data structure in Python: ``` In [1]: print(vertices) [[-1. 0.5 0.5] [ 1. 0.5 0.5] [ 0. -0.5 0.5] [-1. 0.5 0.5] [ 1. 0.5 0.5] [ 0. 0.5 -0.5] [-1. 0.5 0.5] [ 0. -0.5 0.5] [ 0. 0.5 -0.5] [ 1. 0.5 0.5] [ 0. -0.5 0.5] [ 0. 0.5 -0.5]] ``` The rows are points in 3D. Note that there are only four distinct points. If we divide the matrix into sets of three rows, each triplet defines a triangle. These four triangles form the faces of an irregular tetrahedron. This is a standard way to define a 3D object. More complex objects need more triangles. Note that the textbook have vertices as *column* vectors, while we here use row vectors. This means that we need to transpose matrices when we translate textbook formulæ to python formulæ. Consider, for instance a rotation matrix $$ \begin{align} R = \begin{bmatrix} \cos(\pi/6) & -\sin(\pi/6) & 0 \\ \sin(\pi/6) & \cos(\pi/6) & 0 \\ 0 & 0 & 1 \end{bmatrix} \end{align} $$ ```python from numpy import * R = array( [[ cos(pi/6), -sin(pi/6), 0], [ sin(pi/6), cos(pi/6), 0], [0,0,1]] ) ``` This matrix is orthonormal, which we can check: ```python matmul( R, transpose(R) ) ``` To rotate a vector $\vec{v}$, we would calculate $\vec{u}= R\cdot \vec{v}$, where $\vec{u}$ and $\vec{v}$ are column vectors. If you have a shape $V$ where the columns are points, it could be rotated as $U=R\cdot V$. In python this has to become ```python V = transpose(vertices) U = matmul(R, V) newvertices = transpose(U) ``` Translation is a little simpler, as we can do ```python newvertices = vertices + array([1,1,0]) ``` Recall, to visualise, ```python ob1 = Poly3DCollection(vertices, linewidths=1, alpha=0.2) ob2 = Poly3DCollection(newvertices, linewidths=1, alpha=0.2) ob1.set_facecolor( [1, 0.5, 0.5] ) ob2.set_facecolor( [0.5, 1, 0.5] ) ob1.set_edgecolor([0,0,0]) ob2.set_edgecolor([0,0,0]) ax.add_collection3d(ob1) ax.add_collection3d(ob2) plt.show() ``` ## Homogeneous co-ordinates As we know, a lot of operations are simpler in homogeneous co-ordinates. We can add a one to the vertices as follows: ```python In [2]: vertices.shape Out[2]: (12, 3) In [3]: hvertices = hstack( [vertices, ones([12,1])] ) In [4]: print( hvertices) [[-1. 0.5 0.5 1. ] [ 1. 0.5 0.5 1. ] [ 0. -0.5 0.5 1. ] [-1. 0.5 0.5 1. ] [ 1. 0.5 0.5 1. ] [ 0. 0.5 -0.5 1. ] [-1. 0.5 0.5 1. ] [ 0. -0.5 0.5 1. ] [ 0. 0.5 -0.5 1. ] [ 1. 0.5 0.5 1. ] [ 0. -0.5 0.5 1. ] [ 0. 0.5 -0.5 1. ]] In [5]: ``` Suppose vi have the rotational matrix $R$ as above, and want to translate by a vector $\vec{a} = [1,1,0]^T$. We can build the transform matrix as ```python a = array( [[1,1,0]] ) H1 = hstack( [R, transpose(a)] ) H = vstack( [ H1, array([0,0,0,1] ) ] ) ``` Using this transform, we can transform the vertices in homogeneous co-ordinates, and also retrieve an inhomogeneous form. ```python newhvertices = transpose( matmul( H, transpose(hvertices) ) ) newvertices = newhvertices[:,:3] ``` ## STL files and the STL library + [A simple tutorial](https://spltech.co.uk/how-to-create-a-3d-model-of-a-photo-using-python-numpy-and-google-colab-part-i/) STL is a standard format for writing surface meshes. The following example defines the same tetrahedron as we have been working with, but the creates a mesh object using the numpy-stl library. ```python import numpy as np from stl import mesh corners = [ [-1,0.5,0.5], [+1,0.5,0.5], [0,-0.5,0.5], [0,0.5,-0.5] ] vertices = np.array( corners ) face1 = [ 0, 1, 2 ] face2 = [ 0, 1, 3 ] face3 = [ 0, 2, 3 ] face4 = [ 1, 2, 3 ] faces = np.array( [ face1, face2, face3, face4 ] ) th = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype)) for i, f in enumerate(faces): for j in range(3): print( (i,j), vertices[f[j],:]) th.vectors[i][j] = vertices[f[j]] th.save('tetrahedron.stl') ``` Here, we save it to file. We can also show it. ```python from matplotlib import pyplot as plt from mpl_toolkits.mplot3d.art3d import Poly3DCollection ob = Poly3DCollection(th.vectors, linewidths=1, alpha=0.2) ob.set_facecolor( [0.5, 0.5, 1] ) ob.set_edgecolor([0,0,0]) plt.ion() fig = plt.figure() ax = fig.add_subplot(111, projection='3d') plt.show() ax.add_collection3d(ob) ``` This is essentially the same code as we have used before, just note how we access the vectors from the `th` object. Note that the `th.vectors` array is three-dimensional: ``` In [2]: cube.vectors Out[2]: array([[[-1. , 0.5, 0.5], [ 1. , 0.5, 0.5], [ 0. , -0.5, 0.5]], [[-1. , 0.5, 0.5], [ 1. , 0.5, 0.5], [ 0. , 0.5, -0.5]], [[-1. , 0.5, 0.5], [ 0. , -0.5, 0.5], [ 0. , 0.5, -0.5]], [[ 1. , 0.5, 0.5], [ 0. , -0.5, 0.5], [ 0. , 0.5, -0.5]]], dtype=float32) ``` This makes it a lot more difficult to use low-level matrix operations to rotate and translate the object. # Exercise ## Rotations and translations ## Homogenous Coordinates Motion defined by homogenous matrix ## STL files and the STL library + Load Model + View Model + Change Model + Save Model # Additional Materials + [Other relevant python libraries](Python3D)