#! /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 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 = np.array([ p0[0], p0[1], p0[2], 1.0 ]) xyz1 = np.array([ 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 np.array([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: draw X,Y axes # ------------------------------------------------------------------- def draw_xy_axes(win,tickmarks,linewidth=1,linecolor="black"): wx = win.width # window width wy = win.height # window height wcx = round(wx/2.0) # window center X wcy = round(wy/2.0) # window center Y # ---- X axis xl = Line(Point(0,wcy),Point(wx-1,wcy)) xl.setWidth(linewidth) xl.setFill(linecolor) xl.draw(win) # ---- Y axis yl = Line(Point(wcx,0),Point(wcx,wy-1)) yl.setWidth(linewidth) yl.setFill(linecolor) yl.draw(win) # ---- tick marks? if not tickmarks: return x1 = wcx # X axis center x2 = wcx # X axis center y1 = wcy # Y axis center y2 = wcy # Y axis center xt1 = wcy - 5 # X axis tick start xt2 = wcy + 6 # Y axis tick end yt1 = wcx - 5 # X axis tick start yt2 = wcx + 6 # Y axis tick end for _ in range(7): x1 += 50 t = Line(Point(x1,xt1),Point(x1,xt2)) t.setWidth(linewidth) t.setFill(linecolor) t.draw(win) x2 -= 50 t = Line(Point(x2,xt1),Point(x2,xt2)) t.setWidth(linewidth) t.setFill(linecolor) t.draw(win) y1 += 50 t = Line(Point(xt1,y1),Point(xt2,y1)) t.setWidth(linewidth) t.setFill(linecolor) t.draw(win) y2 -= 50 t = Line(Point(xt1,y2),Point(xt2,y2)) t.setWidth(linewidth) t.setFill(linecolor) t.draw(win) return # ------------------------------------------------------------------- # ---- 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 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()