• Search in book...
• Toggle Font Controls
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
M. Cywiak, D. CywiakMulti-Platform Graphics Programming with Kivyhttps://doi.org/10.1007/978-1-4842-7113-1_10

# 10. Stereoscopic parametric 3D Plots

(1)
Leon, Guanajuato, Mexico
(2)
Queretaro, Mexico

In this chapter, we present basic elements for constructing parametric 3D plots. A screenshot of a circular helix generated with this program running on an Android cell phone is shown in Figure 10-1.

The buttons have the same functionality as described in Chapter 7.

## 10.1 Generating the Function

The code to generate the circular helix is shown in Listing 10-1.
#Number of pixels for representing the function
N=100;
x=np.zeros(N+1); z=np.zeros(N+1); y=np.zeros(N+1);
L=50;  Pi=np.pi;
for n in range (0,N+1):
x[n]=L*np.sin(6*Pi/N*n);
z[n]=L*np.cos(6*Pi/N*n) +D/2;
for n in range(0,N+1):
y[n]=n/N*100;
Listing 10-1

Generating the Circular Helix

## 10.2 Creating the PIL Images for the Stereoscopic Effect

The Initialize(B, *args) function obtains the dimensions of the screen and creates PilImage1 and PilImage2. Additionally, the function creates the instances Draw1 and Draw2, where the left and right plots will be drawn. A TrueType Font (TTF) is selected to display the 3D Images text when the program starts. The code for Initialize(B, *args) is shown in Listing 10-2.
def Initialize(B, *args):
global W,H, XC,YC;
global PilImage1,PilImage2, Draw1,Draw2;
# P= Percentage of the D distance
global P, Amplitude;
W,H=B.ids.Screen1.size;
XC=int (W/2)+20;
YC=int(H/2)+60;
PilImage1= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw1 = ImageDraw.Draw(PilImage1);
PilImage2= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw2 = ImageDraw.Draw(PilImage2);
Font = ImageFont.truetype('Gargi.ttf', 70);
Draw1.text( (30,200), "3D Images", fill =(
255,0,0,1), font=Font);
Draw2.text( (50,200), "3D Images", fill =
(0,255,255,1), font=Font);
ShowScene(B);
Listing 10-2

The Initialize Function

## 10.3 Plotting the Function

As described in Chapter 8, drawing memory on the screen is a time-consuming process. Therefore, it is convenient to allocate a memory region of the appropriate size to store the complete set of consecutive points for circular helix. This way, we can draw the plot with only one access to the screen memory, reducing the processing time.

We create the array by means of this directive:
PointList=np.zeros( (N+1,2) )
Next, we fill the array with the list of points. After this, we use this directive to convert the data stored in the array into the appropriate tuple list required by PIL for drawing:
List=tuple( map(tuple,PointList) )
Finally, the following directive draws the plot:
Draw.line( List, fill=(r,g,b), width=6 )

As the helix aspect is similar to a wire, for better visualization, we set the drawing line width equal to 6.

These directives are enclosed in the GraphFunction(VX, VY, VZ, Which) function. This function receives the parameters corresponding to the left or to the right projections, depending on whether the variable Which receives the value 0 or 1. The code for GraphFunction(VX, VY, VZ, Which) is shown in Listing 10-3.
PointList=np.zeros( (N+1,2) );
def GraphFunction(VX,VY,VZ,Which):
global x,y, z, N;
if (Which==0):
r,g,b = 250, 0, 0; #red Image
Draw=Draw1
else:
r,g,b= 0, 250, 250; #cyan image
Draw=Draw2
for n in range (0,N+1):    #Drawing
Factor=(D-VZ)/(D-z[n]-VZ);
xA=XC+Factor*(x[n]-VX)+VX+ShiftX;
yA=YC-Factor*(y[n]-VY)-VY+ShiftY;
PointList[n]=xA,yA;
List=tuple( map(tuple,PointList) );
Draw.line( List, fill=(r,g,b), width=6 );
Listing 10-3

Code for Plotting the Function

This code generates the left and right images with the required plots. Now, the ShowScene(B) function will create the necessary arrays for ORing the PIL images and displaying the result on the screen, in the same manner as described in previous chapters. The code for ShowScene(B) is shown in Listing 10-4.
def ShowScene(B):
Array1=np.array(PilImage1);
Array2=np.array(PilImage2);
Array3=Array1 | Array2;
PilImage3=Image.fromarray(Array3);
Memory=io.BytesIO();
PilImage3.save(Memory, format="png");
Memory.seek(0);
ImagePNG=CoreImage(Memory, ext="png");
B.ids.Screen1.texture=ImagePNG.texture;
ImagePNG.remove_from_cache()
Memory.close();
PilImage3.close();
Array1=None;
Array2=None;
Array3=None;
Listing 10-4

Code for Displaying the Scene

The code for main.py and File.kv are shown in Listings 10-5 and 10-6, respectively.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Line, Color
from kivy.clock import Clock
from kivy.core.image import Image as CoreImage
from PIL import Image, ImageDraw, ImageFont
import io
import os
import numpy as np
from kivy.lang import Builder
os.path.join(os.path.dirname(os.path.abspath(
__file__)), 'File.kv')
);
#Avoid Form1 of being resizable
from kivy.config import Config
Config.set("graphics","resizable", False);
Config.set('graphics', 'width',  '480');
Config.set('graphics', 'height', '680');
D=1000;
VX1=180; VY1=200; VZ1=0;
VX2=200; VY2=200; VZ2=0;
ShiftX=VX1; ShiftY=-VY1;
#Number of pixels for representing the function
N=100;
x=np.zeros(N+1); z=np.zeros(N+1); y=np.zeros(N+1);
L=50;  Pi=np.pi;
for n in range (0,N+1):
x[n]=L*np.sin(6*Pi/N*n);
z[n]=L*np.cos(6*Pi/N*n) +D/2;
for n in range(0,N+1):
y[n]=n/N*100;
PointList=np.zeros( (N+1,2) );
def GraphFunction(VX,VY,VZ,Which):
global x,y, z, N;
if (Which==0):
r,g,b = 250, 0, 0; #red Image
Draw=Draw1
else:
r,g,b= 0, 250, 250; #cyan image
Draw=Draw2
for n in range (0,N+1):    #Drawing
Factor=(D-VZ)/(D-z[n]-VZ);
xA=XC+Factor*(x[n]-VX)+VX+ShiftX;
yA=YC-Factor*(y[n]-VY)-VY+ShiftY;
PointList[n]=xA,yA;
List=tuple( map(tuple,PointList) );
Draw.line( List, fill=(r,g,b), width=6 );
def ShowScene(B):
Array1=np.array(PilImage1);
Array2=np.array(PilImage2);
Array3=Array1 | Array2;
PilImage3=Image.fromarray(Array3);
Memory=io.BytesIO();
PilImage3.save(Memory, format="png");
Memory.seek(0);
ImagePNG=CoreImage(Memory, ext="png");
B.ids.Screen1.texture=ImagePNG.texture;
ImagePNG.remove_from_cache()
Memory.close();
PilImage3.close();
Array1=None;
Array2=None;
Array3=None;
def ClearObjects():
Draw1.rectangle( (0, 0, W-10, H-10),
fill=(60, 70, 30, 1) );
Draw2.rectangle( (0, 0, W-10, H-10),
fill=(60, 70, 30, 1) );
def RotateFunction(B, Sense):
global x,y,z,D,N
if Sense==-1:
Teta=np.pi/180*(-0.1);
else:
Teta=np.pi/180*(0.1);
Cos_Teta=np.cos(Teta)
Sin_Teta=np.sin(Teta);
X0=0;  Y0=0;  Z0=D/2 # Center of rotation
for n in range(0,N+1):
for m in range(0,N+1):
if (B.ids.Button3.state=="down" or
B.ids.Button4.state=="down"):
yP=(y[n]-Y0)*Cos_Teta
+ (x[n]-X0)*Sin_Teta +Y0;
xP=-(y[n]-Y0)*Sin_Teta
+(x[n]-X0)*Cos_Teta + X0;
y[n]=yP;
x[n]=xP;
if (B.ids.Button5.state=="down" or
B.ids.Button6.state=="down"):
yP=(y[n]-Y0)*Cos_Teta
+ (z[n]-Z0)*Sin_Teta + Y0;
zP=-(y[n]-Y0)*Sin_Teta
+(z[n]-Z0)*Cos_Teta + Z0;
y[n]=yP;
z[n]=zP;
if (B.ids.Button7.state=="down" or
B.ids.Button8.state=="down"):
xP=(x[n]-X0)*Cos_Teta
+ (z[n]-Z0)*Sin_Teta + X0;
zP=-(x[n]-X0)*Sin_Teta
+(z[n]-Z0)*Cos_Teta + Z0;
x[n]=xP;
z[n]=zP;
class Form1(FloatLayout):
def __init__(Handle, **kwargs):
super(Form1, Handle).__init__(**kwargs);
Event1=Clock.schedule_once(
Handle.Initialize);
def Initialize(B, *args):
global W,H, XC,YC;
global PilImage1,PilImage2, Draw1,Draw2;
# P= Percentage of the D distance
global P, Amplitude;
W,H=B.ids.Screen1.size;
XC=int (W/2)+20;
YC=int(H/2)+60;
PilImage1= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw1 = ImageDraw.Draw(PilImage1);
PilImage2= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw2 = ImageDraw.Draw(PilImage2);
Font = ImageFont.truetype('Gargi.ttf', 70);
Draw1.text( (30,200), "3D Images", fill =(
255,0,0,1), font=Font);
Draw2.text( (50,200), "3D Images", fill =
(0,255,255,1), font=Font);
ShowScene(B);
def Button1_Click(B):
global Draw1, Draw2;
ClearObjects(); # Clearing Draw1 and Draw2
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button2_Click(B):
ClearObjects(); # Clearing Draw1 and Draw2
Font = ImageFont.truetype('Gargi.ttf', 70)
Draw1.text( (30,200), "3D Images", fill =
(255,0,0,1), font=Font);
Draw2.text( (50,200), "3D Images", fill =
(0,255,255,1), font=Font);
ShowScene(B);
def Button3_Click(B):
RotateFunction(B,1);
ClearObjects(); # Clearing Draw1 and Draw2
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button4_Click(B):
RotateFunction(B,-1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button5_Click(B):
RotateFunction(B,-1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button6_Click(B):
RotateFunction(B,1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button7_Click(B):
RotateFunction(B,-1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
def Button8_Click(B):
RotateFunction(B,1),
ClearObjects();
GraphFunction(VX1,VY1,VZ1,0);
GraphFunction(VX2,VY2,VZ2,1);
ShowScene(B);
# This is the Start Up code.
class StartUp (App):
def build (BU):
BU.title="Form1"
return Form1();
if __name__ =="__main__":
StartUp().run();
Listing 10-5

Code for the main.py File

#:set W 440
#:set H 440
<Form1>:
id : Form1
Image:
id: Screen1
size_hint: None,None
pos_hint: {"x":0.04, "y":0.34}
size: W,H
canvas.before:
Color:
rgba: 0.9, 0.9, 0, 1
RoundedRectangle:
pos:  self.pos
size: self.size
Button:
id: Button1
on_press: Form1.Button1_Click()
text: "Button1"
size_hint: None,None
pos_hint: {"x": 0.2, "y":0.03}
size: 100,30
Button:
id: Button2
on_press: Form1.Button2_Click()
text: "Button2"
size_hint: None,None
pos_hint: {"x": 0.63, "y":0.03}
size: 100,30
Button:
id: Button3
on_press: Form1.Button3_Click()
text: "Button3"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.12}
size: 100,30
always_release: True
Button:
id: Button4
on_press: Form1.Button4_Click()
text: "Button4"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.12}
size: 100,30
Button:
id: Button5
on_press: Form1.Button5_Click()
text: "Button5"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.20}
size: 100,30
Button :
id: Button6
on_press: Form1.Button6_Click()
text: "Button6"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.20}
size: 100,30
Button:
id: Button7
on_press: Form1.Button7_Click()
text: "Button7"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.28}
size: 100,30
Button:
id: Button8
on_press: Form1.Button8_Click()
text: "Button8"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.28}
size: 100,30
Listing 10-6

Code for the file.kv File

When plotting curves described by parametric equations, it can be hard to follow their paths from the proper perspective. You can see this in the plot in Figure 10-2. For these cases, stereoscopy can provide a better perception of the graph. The code to generate this function is shown in Listing 10-7.
for n in range (0,N+1):
x[n]=L*np.sin(6*Pi/N*n);
z[n]=L*np.cos(6*Pi/N*n);
y[n]=60*np.sin(4*Pi/N*n)
Listing 10-7

Another Parametric Function Used as an Illustrative Example

## 10.4 Summary

In this chapter, we described elements for constructing stereoscopic parametric 3D plots. We also included two working examples.

• No Comment
..................Content has been hidden....................