#! /usr/bin/python3
# ===================================================================
# test graphics transformations
# translate/rotate/scale wire frame cube
#
# This program demonstrates a transformation matrix. A transformation
# matrix transforms the coordinates of points defining a wire frame
# graphics object. In this way you can rotate and translate the wire
# frame object.
#
# Multiple transformation matrices can be collected into one
# transformation matrix for use. This is more efficient than
# multiple transforming matrices.
#
# Center (Cartesian) coordinates are used by the program and
# only converted to window coordinates when it is time to draw
# something in the graphics window.
#
# In modern computers transformations are done in special graphics
# hardware (GPU - Graphics Processing Unit).
#
# ===================================================================
import coordinate_conversion as cc
import transformation_matrix as tm
import user_interface as ui
import draw_xy_axes as ax
from graphics import *
import numpy as np
import copy
import re, os, platform, sys
win_height = 801 # window height
win_width = 801 # window width
# -------------------------------------------------------------------
# ---- graphics (wire frame) object
# -------------------------------------------------------------------
# ---- wire frame (cube) corner points
pts = [ (-50,-50,50), (-50,50,50), (50,50,50), (50,-50,50),
(-50,-50,-50), (-50,50,-50), (50,50,-50), (50,-50,-50) ]
pivot = (0, 0, 0)
# ---- wire frame (cube) lines
lns = [ (pts[0],pts[1]), (pts[1],pts[2]),
(pts[2],pts[3]), (pts[3],pts[0]),
(pts[4],pts[5]), (pts[5],pts[6]),
(pts[6],pts[7]), (pts[7],pts[4]),
(pts[0],pts[4]), (pts[1],pts[5]),
(pts[2],pts[6]), (pts[3],pts[7]) ]
# -------------------------------------------------------------------
# ---- this class is used as a data store for the wire frame
# ---- it also accumulates the transformation matrices
# -------------------------------------------------------------------
class WireFrame:
# ---- Internal function to convert wire frame lines into lists
# ---- (arrays of points) used by the transformation matrix.
# ---- each line has two points (start,end)
# ---- each point has a three axes values (x,y,z)
# ---- X,Y,Z are center (Cartesian) coordinates.
def _lns_to_mx(self,lns):
mxlns = []
for l in lns:
# ---- line points (start,end)
p0 = l[0]
p1 = l[1]
# ---- line points for transformation matrix (start,end)
xyz0 = [ p0[0], p0[1], p0[2], 1.0 ]
xyz1 = [ p1[0], p1[1], p1[2], 1.0 ]
mxlns.append([xyz0,xyz1])
return mxlns
def __init__(self,lns,pivot):
self.x = pivot[0] # wire frame x pivot point
self.y = pivot[1] # wire frame y pivot point
self.z = pivot[2] # wire frame z pivot point
self.lns = lns # wire frame lines
self.mtrx = np.identity(4)
self.mxlines = self._lns_to_mx(self.lns)
def reset(self):
self.mtrx = np.identity(4)
self.mxlines = self._lns_to_mx(self.lns)
def set_pivot_point(self,x,y,z):
self.x = x # wireframe x location
self.y = y # wireframe y location
self.z = z # wireframe z location
def reset_matrix(self):
self.mtrx = np.identity(4)
def get_mx_lines(self):
return self.mxlines
def get_x(self): # wire frame x location
return self.x
def get_y(self): # wire frame y location
return self.y
def get_z(self): # wire frame z location
return self.z
def get_xyz1(self): # wire frame [xyz1] location
return [self.x, self.y, self.z, 1.0]
def translate_to_location(self,dx,dy,dz):
self.x += dx # wire frame x pivot point
self.y += dy # wire frame y pivot point
self.z += dz # wire frame z pivot point
m = tm.get_translation_matrix_3d(dx,dy,dz)
mm = m @ self.mtrx
self.mtrx = mm
def translate_to_origin(self):
m = tm.get_translation_matrix_3d(-self.x,-self.y,-self.z)
mm = m @ self.mtrx
self.mtrx = mm
self.x = pivot[0] # wire frame x pivot point
self.y = pivot[1] # wire frame y pivot point
self.z = pivot[2] # wire frame z pivot point
def rotate_around_x_axis(self,degrees):
m = tm.get_x_rotation_matrix_3d(degrees)
mm = m @ self.mtrx
self.mtrx = mm
def rotate_around_y_axis(self,degrees):
m = tm.get_y_rotation_matrix_3d(degrees)
mm = m @ self.mtrx
self.mtrx = mm
def rotate_around_z_axis(self,degrees):
m = tm.get_z_rotation_matrix_3d(degrees)
mm = m @ self.mtrx
self.mtrx = mm
def scale_xyz(self,sx,sy,sz):
m = tm.get_scaling_matrix_3d(sx,sy,sz)
mm = m @ self.mtrx
self.mtrx = mm
def get_matrix(self):
return copy.deepcopy(self.mtrx)
def display_matrix(self):
print(self.mtrx)
def display_mx_lines(self):
print('-------------------------------------------------')
for l in self.mxlines:
p0 = f'[{l[0][0]}, {l[0][1]}, {l[0][2]}]'
p1 = f'[{l[1][0]}, {l[1][1]}, {l[1][2]}]'
print(f'ln: {p0}, {p1}')
print('-------------------------------------------------')
#--------------------------------------------------------------------
# ---- Function: create a line
# -------------------------------------------------------------------
def create_line(win, mtrx, ln, w=2, c="black"):
p0 = mtrx @ ln[0] # start of line
p1 = mtrx @ ln[1] # end of line
xy0 = cc.center_to_win_coords(p0[0],p0[1],win.width,win.height)
xy1 = cc.center_to_win_coords(p1[0],p1[1],win.width,win.height)
lobj = Line(Point(xy0[0],xy0[1]), Point(xy1[0],xy1[1]))
lobj.setWidth(w)
lobj.setFill(c)
return lobj
# -------------------------------------------------------------------
# ---- Function: draw wire frame lines
# -------------------------------------------------------------------
def draw_wireframe(win, wireframe, mxlines, winlines):
mtrx = wireframe.get_matrix()
for ln in mxlines:
lobj = create_line(win,mtrx,ln)
lobj.draw(win)
winlines.append(lobj)
# -------------------------------------------------------------------
# ---- Function: translate (transformation matrix)
# -------------------------------------------------------------------
def translate_wireframe(win, wireframe, command):
if command == 't0':
wireframe.translate_to_origin()
return True
x = command.split()
if len(x) != 2:
print()
print(f'oops! bad command ({command})')
return False
cmd = x[0]
(tflag,dist) = ui.is_float(x[1])
if not tflag:
print()
print(f'oops! bad command ({command})')
return False
if cmd == 'tx':
wireframe.translate_to_location(dist,0.0,0.0)
elif cmd == 'ty':
wireframe.translate_to_location(0.0,dist,0.0)
elif cmd == 'tz':
wireframe.translate_to_location(0.0,0.0,dist)
else:
print()
print(f'oops! bad command ({command})')
return False
return True
# -------------------------------------------------------------------
# ---- Function: rotate (transformation matrix)
# -------------------------------------------------------------------
def rotate_wireframe(win, wireframe, command):
x = command.split()
if len(x) != 2:
print()
print(f'oops! bad command ({command})')
return False
cmd = x[0]
(tflag,deg) = ui.is_float(x[1])
if not tflag:
print()
print(f'oops! bad command ({command})')
return False
if cmd == 'rx':
wireframe.rotate_around_x_axis(deg)
elif cmd == 'ry':
wireframe.rotate_around_y_axis(deg)
elif cmd == 'rz':
wireframe.rotate_around_z_axis(deg)
else:
print()
print(f'oops! bad command ({command})')
return False
return True
# -------------------------------------------------------------------
# ---- Function: scale (scale matrix)
# -------------------------------------------------------------------
def scale_wireframe(win, wireframe, command):
x = command.split()
if len(x) != 4:
print()
print(f'oops! bad command ({command})')
return False
(tflag,sx) = ui.is_float(x[1])
if not tflag:
print()
print(f'oops! bad command ({command})')
return False
(tflag,sy) = ui.is_float(x[2])
if not tflag:
print()
print(f'oops! bad command ({command})')
return False
(tflag,sz) = ui.is_float(x[3])
if not tflag:
print()
print(f'oops! bad command ({command})')
return False
wireframe.scale_xyz(sx,sy,sz)
return True
# -------------------------------------------------------------------
# ---- Function: change wire frame pivot point
# -------------------------------------------------------------------
def change_wireframe_pivot_point(win, wireframe, command):
x = command.split()
if len(x) != 4:
print()
print(f'oops! bad command ({command})')
return False
(tflag,px) = ui.is_float(x[1])
if not tflag:
print()
print(f'oops! bad command ({command})')
return False
(tflag,py) = ui.is_float(x[2])
if not tflag:
print()
print(f'oops! bad command ({command})')
return False
(tflag,pz) = ui.is_float(x[3])
if not tflag:
print()
print(f'oops! bad command ({command})')
return False
wireframe.set_pivot_point(px,py,pz)
return True
# -------------------------------------------------------------------
# ---- Function: erase graphics objects from window
# -------------------------------------------------------------------
def clear_window(objs):
for o in objs:
o.undraw()
# -------------------------------------------------------------------
# ---- main
# -------------------------------------------------------------------
# ---- running Python3
if not ui.running_python3:
print()
print('Must run Python3 - exit program')
print()
sys.exit()
# ---- create window
win = GraphWin("Wire Frame", win_width, win_height)
win.setBackground("white")
# ---- draw X,Y coordinate axes
ax.draw_xy_axes(win,True)
# ---- loop forever
lastcmd = ''
wf = WireFrame(lns,pivot)
mxlines = wf.get_mx_lines()
winlines = []
while True:
# ---- ask the user to make a selection
ui.clear_screen()
print('----------------------------------------------')
print('------------ Transformation Matrix -----------')
print('----------------------------------------------')
print('[qQ] = quit')
print('re = reset to initial conditions')
print('dr = draw wire frame')
print('mx = display transformation matrix')
print('ii = display internal information')
print('mxl = display matrix lines')
print('cw = clear the graphics window (undraw)')
print()
print('------- Build Transformation Matrix --------')
print()
print('tx distance = translate x distance')
print('ty distance = translate y distance')
print('tz distance = translate z distance')
print('t0 = translate to origin (0,0,0)')
print('rx angle = rotate around X axis')
print('ry angle = rotate around Y axis')
print('rz angle = rotate around Z axis')
print('pp x y z = set/change pivot point')
print('sxyz sx xy xz = scale x,y,z')
# ---- ask the user to make a selection
print()
s = ui.get_user_input('Enter command: ')
if not s: # empty string?
break
# ---- display_transformation matrix
if s == 'mx':
print('---------------------------------------')
wf.display_matrix()
print('---------------------------------------')
ui.pause()
continue
# ---- display internal information
if s == 'ii':
print('---------------------------------------')
wf.display_matrix()
print('---------------------------------------')
x = wf.get_x()
y = wf.get_y()
z = wf.get_z()
print(f'location: x={x}, y={y}, z={z}')
print('---------------------------------------')
x = pivot[0]
y = pivot[1]
z = pivot[2]
print(f'pivot: x={x}, y={y}, z={z}')
print('---------------------------------------')
print(f'Length of winlines: {len(winlines)}')
print('---------------------------------------')
ui.pause()
continue
# ---- quit
if s == 'q':
break
# ---- draw wire frame
if s == 'dr':
clear_window(winlines)
winlines = []
draw_wireframe(win,wf,mxlines,winlines)
continue
# ---- reset wireframe
if s == 're':
clear_window(winlines)
wf.reset()
continue
if s == 'cw':
clear_window(winlines)
continue
# ---- display matrix lines
if s == 'mxl':
wf.display_mx_lines()
ui.pause()
continue
# ---- scale wire frame (allow commas and spaces as separators)
if s[0:2] == 'pp':
p = s.replace(',',' ') # convert comma to space
if not change_wireframe_pivot_point(win,wf,p):
ui.pause()
continue
# ---- scale wire frame (allow commas and spaces as separators)
if s[0:4] == 'sxyz':
ss = s.replace(',',' ') # convert comma to space
if not scale_wireframe(win,wf,ss):
ui.pause()
continue
# ---- translate
if s[0] == 't':
if not translate_wireframe(win,wf,s):
ui.pause()
continue
# ---- rotate
if s[0] == 'r':
if not rotate_wireframe(win,wf,s):
ui.pause()
continue
# ---- oops!
print()
print(f'Unknown command ({s})')
ui.pause()
# ---- close window and exit
win.close()