Revision bb870497cb629ba9049217fa62882c9c05fd0838 (click the page title to view the current version)
Changes from bb870497cb629ba9049217fa62882c9c05fd0838 to 27cae4cd11fc38549fe85efa17c5bba33d2b340b
---
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)