Python Cube

../../_images/anim_pycube.png

Animation control:

Visualization

Frame Value

Surface geometry

constant

Surface position

fixed to the coordinate axis

Surface color

constant

Shading and highlighting

illumination direction per frame

Axis coordinate

elev and azim per frame using view_init

Surface construction is based on the Composite of Copies example. Now, both the elev and azim are changed per frame. Changing the axis coordinate and shading/highlighting results in ‘stationary viewer’ perception. The view is rotated about a single surface object. The illumination direction path as the elev and azim changes is shown in the following plot. The starting direction [1,1,1] is shown in red.

../../_images/illum_path.png

In the script that follows, note the following line:

if view_elev >= 270 : view_elev = view_elev - 360

This was needed since the elev argument in the call to ‘view_init’ only provides a unique solution in the domain of -90° to 270°.

import copy
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import matplotlib.patheffects as path_effects
import s3dlib.surface as s3d

#.. Elev and Azim changes setting illumination direction.

# 0. Define animation control parameters ............................

totalTime, f_domain, numFrames = 5, (0.0,1.0), 90   # time in seconds
frames=np.linspace(*f_domain, numFrames, endpoint=False)
interval = int(1000.0*totalTime/numFrames)          # milliseconds

# 2. Setup and map surface .........................................
rez = 6
view_elev, view_azim, illum_dir = 0, 0, [1,1,1] 

top = s3d.PlanarSurface(rez, basetype='oct1')
top.map_color_from_image('data/python.png')
top.map_geom_from_image('data/python_elevation.png',0.07)

front = copy.copy(top)
side = copy.copy(top)
bottom = copy.copy(top)
backside = copy.copy(top)
top.transform(translate=[0,0,1])
backfront = copy.copy(top)
bottom.transform(rotate=s3d.eulerRot(0,180),  translate=[0,0,-1])
front.transform(rotate=s3d.eulerRot(0,90),  translate=[0,-1,0])
backfront.transform(rotate=s3d.eulerRot(180,90),  translate=[0,0,0])
side.transform(rotate=s3d.eulerRot(90,90),  translate=[1,0,0])
backside.transform(rotate=s3d.eulerRot(-90,90),  translate=[-1,0,0])

cube = (top+front+side+bottom+backfront+backside)
orig_cube = copy.copy(cube)
illum = s3d.rtv(illum_dir, view_elev, view_azim)
cube.shade(direction=illum).hilite(.8,direction=illum)

# 3. Construct figure, add surface plot ............................

fig = plt.figure(figsize=plt.figaspect(1), facecolor='black')
ax = plt.axes(projection='3d', facecolor='black', aspect='equal')
ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
ax.set_axis_off()

info = 'Created with S3Dlib 1.3.0, 2024 - https://s3dlib.org'
pblue, pyellow = [0.216,0.443,0.635] , [ 1.0,0.827,0.263]
text = fig.text(0.05, 0.05, info, color=pyellow, fontsize=9  )
text.set_path_effects([path_effects.withSimplePatchShadow(linewidth=3, foreground=pblue)])

ax.add_collection3d(cube)

fig.tight_layout()
#plt.show()

# 4. Animation ======================================================

def update_fig(frame):
    global cube
    cube.remove()
    view_elev = 360*frame
    view_azim = 360*frame
    if view_elev >= 270 : view_elev = view_elev - 360
    ax.view_init( elev=view_elev, azim=view_azim )
    illum = s3d.rtv(illum_dir, view_elev, view_azim)

    cube = copy.copy(orig_cube)
    cube.shade(direction=illum).hilite(.8,direction=illum)
    
    ax.add_collection3d(cube)
    return

anim = FuncAnimation(fig, update_fig, frames, interval=interval, repeat=True)
anim.save('pycube.html',writer='html')

msg = "saved {} frames, values: [{:.3f} to {:.3f}] @ {} milliseconds/frane"
print(msg.format(numFrames,np.min(frames),np.max(frames),interval))