3D Mobile


Establishment date: May 2, 2020

Revision date: May 8, 2020 (revised for tool.py pep8)

Relevant software information:

Win 10 Python 3.7.6 PySimpleGUI 4.18.2 PIL/Pillow 7.1.1


Note: please quote or change this article at will. Just indicate the source and author. The author does not guarantee that the content is absolutely correct. Please be responsible for any consequences


A 3D mobile program, 169 lines

Content description

  1. Fixed vision
  2. road
  3. Sub route
  4. roadside
  5. Road tree
  6. Other green space
  7. Move forward
  8. 3D

Output screen

3D Mobile

Code and description

  1. Import of Library
import PySimpleGUI as sg
import math
from pathlib import Path
from Tool import read_URL, mapping
from PIL import Image
from io import BytesIO
  1. This parameter setting: object length, color and quantity, camera position, screen position,
class GUI():

    def __init__(self):
        self.boxes = 51
        self.box_height = 50
        self.x0, self.y0, self.z0 = 0, 150, -50
        self.z1 = 0
        self.gap_width = 860
        self.road_width = 800
        self.line_width = 20
        self.offset = 0.5
        self.road_color = 'grey'
        self.gap_color = 'black'
        self.line_color = 'white'
        self.figures = []
        self.count = 0
  1. Create canvas object
def Create_Canvas(self):
        self.width = 1080
        self.height = 400
        return sg.Graph((self.width, self.height), (-self.width//2, 0),
            (self.width//2, self.height), key='Graph')
  1. Create a GUI window, download pictures of scenery and trees from the network, and draw the background
def Create_Window(self):
        self.window = sg.Window('3D Road', layout=[[self.Create_Canvas()]],
            return_keyboard_events=True, finalize=True)
        self.draw = self.window.FindElement('Graph')
  1. Download the pictures and save them in the required size
def Download_Picture(self, url, file, size):
        if not Path(file).is_file():
            sg.popup(f'First time to load {file} from web ...', no_titlebar=True,
                auto_close=True, auto_close_duration=2)
            response, data = read_URL(url, byte=True)
            if data:
                with open(file, 'wb') as f:
                im = read_file(file)
                im = im.resize(size)
                sg.popup(f'{file} loaded ...', no_titlebar=True,
                    auto_close=True, auto_close_duration=1)
                sg.popup(f'{file} load failed...', no_titlebar=True,
                    auto_close=True, auto_close_duration=2)
  1. Convert picture files into data suitable for display
def Load_Data(self, file):
        im = Image.open(file)
        with BytesIO() as output:
            im.save(output, format='PNG')
            data = output.getvalue()
        return (data, im.width, im.height)
  1. Load background picture
def Load_Background(self):
        url = 'https://ae01.alicdn.com/kf/HTB18XRYXBv0gK0jSZKb762K2FXaF.png'
        file = 'background.png'
        size = (self.width, 300)
        self.Download_Picture(url, file, size)
        self.background, self.background_width, self.background_height = (
  1. Load Tree Pictures
def Load_Tree(self):
        url = 'https://www.vippng.com/png/full/458-4585828_fall-tree-png.png'
        file = 'tree.png'
        size = (250, 250)
        self.Download_Picture(url, file, size)
        self.tree = Image.open(file)
        self.tree_width, self.tree_height = self.tree.size
  1. Convert 3D coordinates to 2D coordinates
def Point_To_2D(self, point):
        x, y, z = point
        s1 = (z-self.z1)/(z-self.z0)
        X = x + (self.x0 - x)*s1 - self.x0
        Y = y + (self.y0 - y)*s1
        return [X, Y]
  1. According to the coordinates of the lower left corner and their changes, the four angular coordinates of the box are generated
def Point_To_Box(self, x, y, z, dx, dy, dz, dx2):
        return [[x, y, z], [x+dx, y, z],
                [x+dx+dx2, y+dy, z+dz], [x+dx2, y+dy, z+dz]]
  1. Show background picture
def Draw_Background(self):
        self.background_figure = self.draw.DrawImage(data=self.background,
            location=(-self.background_width//2, self.height))
  1. Displays a colored box
def Draw_Box(self, x, y, z, dx, dy, dz, dx2, color):
        points = self.Point_To_Box(x, y, z, dx, dy, dz, dx2)
        points = mapping(self.Point_To_2D, points)
        self.figures.append(self.draw.DrawPolygon(points, fill_color=color))
  1. Show green space
def Draw_Grass(self, x, y, z, dx, dy, dz, dx2, color):
        points = self.Point_To_Box(x, y, z, dx, dy, dz, dx2)
        points = mapping(self.Point_To_2D, points)
        points[0][0] = points[3][0] = -self.width//2
        points[1][0] = points[2][0] = self.width//2
        self.figures.append(self.draw.DrawPolygon(points, fill_color=color))
  1. Show the trees on the left and right and zoom out by ratio
def Draw_Tree(self, x, y, z, dx, dy, dz, scale=1):
        w = max(1, int(self.tree_width*scale))
        h = max(1, int(self.tree_height*scale))
        im = self.tree.resize((w, h))
        with BytesIO() as output:
            im.save(output, format='PNG')
            data = output.getvalue()
        X, Y = self.Point_To_2D([x, y, z])
        figure = self.draw.DrawImage(data=data,
            location = (X-self.tree_width*scale/2, Y+self.tree_height*scale))
        X, Y = self.Point_To_2D([x+dx, y+dy, z+dy])
        figure = self.draw.DrawImage(data=data,
            location = (X-self.tree_width*scale/2, Y+self.tree_height*scale))
  1. Delete all displayed objects in the record
def Delete_Figures(self):
        for figure in self.figures:
        self.figures = []
  1. Screen update
  • Delete the painted object first
  • Painting green space
  • Draw the roadside, road, center line and trees from far to near
  • The central position of the road is adjusted by sin function
  • The height of the road is also adjusted by the sin function
  • The magnification ratio of the tree also varies with distance
def Update_All(self):
        self.Draw_Grass(-self.width//2, 0, self.offset,
            self.width, 0, self.boxes*self.box_height, 0, 'green')
        for box in range(self.boxes-1, -1, -1):
            center = -500*math.sin(2*(box+self.count)/self.boxes*math.pi)
            dx = -500*math.sin(2*(box+self.count+1)/self.boxes*math.pi)-center
            dy = center/2-250
            z = box*self.box_height
            if (box+self.count)%3 == 0:
                self.Draw_Box(center-self.gap_width//2, 0, z, self.gap_width,
                    0, self.box_height, dx, self.gap_color)
            self.Draw_Box(center-self.road_width//2, 0, z, self.road_width,
                    0, self.box_height, dx, self.road_color)
            if (box+self.count)%3 == 0:
                self.Draw_Box(center-self.line_width//2, 0, z, self.line_width,
                    0, self.box_height, dx, self.line_color)
            if (box+self.count)%10 == 0:
                scale = 1- (z-self.z1)/(z-self.z0)
                self.Draw_Tree(center-self.gap_width//2-50, 0, z,
                    self.gap_width+100, 0, 0, scale)
        self.count += 1
        if self.count == self.boxes:
            self.count = 0
        self.x0 = center
  1. Simple GUI event processing loop
G = GUI()
while True:

    event, values = G.window.Read(timeout=100)

    if event == None:

    elif event in ['Left:37', 'Right:39', 'Up:38', 'Down:40']:


This work adoptsCC agreement, reprint must indicate the author and the link to this article

Jason Yang

Recommended Today

Based on vue3 + typescript + vue-cli4 0 build mobile end template scaffold

vue3-h5-template Based on vue3 + typescript + vue-cli4 0 + vant UI + sass + REM adaptation scheme + Axios encapsulation + jssdk configuration + vconsole mobile terminal debugging to build mobile terminal template scaffold Project address:github View demoIt is recommended to check on the mobile terminal Node version requirements Vue CLINode is required JS […]