This article is the 16th day article of Lisp Advent Calendar 2019.
As an introduction to GUI programming in Hy, generative art using the GUI library tkinter included in the Python standard library. To implement.
It is a specification that you can change the parameters by key input in drawing a figure using a trigonometric function like this.
The author's environment is as follows.
The GUI library tkinter is a Python standard library, so Hy is the only third-party module to install in your virtual environment. Hy has installed the latest version as of 12/10 (https://github.com/hylang/hy/tree/39c150d8299d9ff8f47bb78415e22a9fe976d7e5).
cd %USERPROFILE$\.virtualenvs
py -3 -m venv venv_hy_geometric_pattern
cd venv_hy_geometric_pattern
Scripts\activate.bat
pip install --upgrade pip
pip install git+https://github.com/hylang/hy.git
deactivate
tk.Tk () Create a Canvas in the window created as an instance and draw a shape in it. Redraw the Canvas at approximately 60 fps for animation. The coordinates of a point at a certain time are calculated by the following formula. Change $ \ theta $ by $ \ mathrm {d} \ theta $ to create an array of points at that time.
\left\{ \begin{array}{}
x(\theta , t) = x_{\mathrm{center}} + r \cos \theta + a \cos (\phi \left( t \right) + c \theta) \\
y(\theta , t) = y_{\mathrm{center}} + r \sin \theta + a \sin (\phi \left( t \right) + c \theta)
\end{array} \right.
0 \leq \theta < 2 \pi \\
0 \leq \phi < 2 \pi \\
0 \leq c < 2 \pi \\
0 < r + a < \min(x_{\mathrm{center}}, y_{\mathrm{center}}) \\
$ \ left (x_ {\ mathrm {center}}, y_ {\ mathrm {center}} \ right) $ is the center coordinate of the Canvas. $ \ phi $ causes $ 0 \ leq \ phi <2 \ pi $ to loop over time.
Allows you to change the step size $ \ mathrm {d} \ theta $ of $ r, a, c $ and $ \ theta $ above by key input. The key assignments are shown in the table below. It also sets the q key to close and exit the window.
variable | Decrease key | Increase key |
---|---|---|
z | a | |
x | s | |
c | d | |
v | f |
hy
Suppose the directory has been added to pythonpath.
The contents of \ _ \ _ init \ _ \ _. Hy are empty, and we will implement the function in gui.hy.
Call and execute geometric_pattern.gui from geometric_pattern.bat.
hy ─┬─ geometric_pattern ─┬─ __init__.hy
│ │
│ └─ gui.hy
│
└─ geometric_pattern.bat
gui.hy
(import [math [sin cos radians]])
(import sys)
(import [tkinter :as tk])
;Canvas size
(setv *width* 800)
(setv *height* 600)
(defclass GeometricPattern []
(setv *d-theta-max* 20)
(setv *deg* 360)
(setv *d-phi* 1)
(defn --init-- [self &optional [center-x 0] [center-y 0]]
"Initialize the parameters and get a list of points"
(setv (. self center-x) center-x)
(setv (. self center-y) center-y)
(setv (. self r)
(int (-> 0.8
(* (min (. self center-x) (. self center-y)))
(/ 3))))
(setv (. self a)
(int (-> 0.8
(* 2 (min (. self center-x) (. self center-y)))
(/ 3))))
(setv (. self phi) 0)
(setv (. self c) 104)
;;Step width when changing theta
(setv (. self d-theta) 1)
((. self fetch-points)))
(defn dec-r [self event]
"Decrease r"
(setv (. self r) (dec (. self r)))
(if (< (. self r) 1)
(setv (. self r) 1)))
(defn inc-r [self event]
"Increase r. The upper limit is specified so that it does not extend beyond the short side of Canvas."
(if (< (+ (. self r) (. self a))
(min (. self center-x) (. self center-y)))
(setv (. self r) (inc (. self r)))))
(defn dec-a [self event]
"Decrease a"
(setv (. self a) (dec (. self a)))
(if (< (. self a) 1)
(setv (. self a) 1)))
(defn inc-a [self event]
"Increase a. The upper limit is specified so that it does not extend beyond the short side of Canvas."
(if (< (+ (. self r) (. self a))
(min (. self center-x) (. self center-y)))
(setv (. self a) (inc (. self a)))))
(defn dec-d-theta [self event]
(setv (. self d-theta) (dec (. self d-theta)))
(if (< (. self d-theta) 1)
(setv (. self d-theta) 1)))
(defn inc-d-theta [self event]
(setv (. self d-theta) (inc (. self d-theta)))
(if (> (. self d-theta) (. GeometricPattern *d-theta-max*))
(setv (. self d-theta) (. GeometricPattern *d-theta-max*))))
(defn dec-c [self event]
(setv (. self c) (dec (. self c)))
(if (< (. self c) 0)
(setv (. self c) (% (. self c) (. GeometricPattern *deg*)))))
(defn inc-c [self event]
(setv (. self c) (inc (. self c)))
(if (>= (. self c) (. GeometricPattern *deg*))
(setv (. self c) (% (. self c) (. GeometricPattern *deg*)))))
(defn inc-phi [self]
(setv (. self phi) (inc (. self phi)))
(setv (. self phi) (% (. self phi) (. GeometricPattern *deg*))))
(defn fetch-points [self]
"Get the coordinates of a point"
(setv (. self points)
(lfor theta (range 0 (. GeometricPattern *deg*) (. self d-theta))
(, (+ (+ (. self center-x) (* (. self r) (cos (radians theta))))
(* (. self a) (cos (radians (+ (. self phi) (* (. self c) theta))))))
(+ (+ (. self center-y) (* (. self r) (sin (radians theta))))
(* (. self a) (sin (radians (+ (. self phi) (* (. self c) theta)))))))))
;;Add the first point to the end of the list to close the path
((. self points append) (. self points [0]))))
(defclass Simulator []
(setv *ms* 16)
(defn --init-- [self width height]
(setv (. self width) width)
(setv (. self height) height)
;;Window and canvas initialization
(setv (. self window) ((. tk Tk)))
((. self window title) :string "Sample Trig Function on Tkinter")
((. self window resizable) :width False :height False)
(setv (. self canvas)
((. tk Canvas) (. self window)
:width (. self width)
:height (. self height)))
(setv (. self center-x) (/ width 2))
(setv (. self center-y) (/ height 2))
;;Coordinates of characters to display
(setv (. self quit-x) 20)
(setv (. self quit-y) 30)
(setv (. self r-x) (- width 20))
(setv (. self r-y) (- height 120))
(setv (. self a-x) (- width 20))
(setv (. self a-y) (- height 90))
(setv (. self c-x) (- width 20))
(setv (. self c-y) (- height 60))
(setv (. self d-theta-x) (- width 20))
(setv (. self d-theta-y) (- height 30))
(setv (. self gp) (GeometricPattern (. self center-x) (. self center-y)))
;;Fill the canvas with the background color (black)
((. self canvas create-rectangle) 0
0
(. self width)
(. self height)
:fill "black")
((. self draw))
((. self canvas pack))
;;Key binding settings
((. self window bind) "<KeyPress-q>" (. self quit))
((. self window bind) "<KeyPress-z>" (. self gp dec-r))
((. self window bind) "<KeyPress-a>" (. self gp inc-r))
((. self window bind) "<KeyPress-x>" (. self gp dec-a))
((. self window bind) "<KeyPress-s>" (. self gp inc-a))
((. self window bind) "<KeyPress-c>" (. self gp dec-c))
((. self window bind) "<KeyPress-d>" (. self gp inc-c))
((. self window bind) "<KeyPress-v>" (. self gp dec-d-theta))
((. self window bind) "<KeyPress-f>" (. self gp inc-d-theta)))
(defn quit [self event]
((. self window destroy)))
(defn draw [self]
;;Drawing of shapes
((. self canvas create-line) (. self gp points) :fill "white" :tag "sample")
;;Character drawing
((. self canvas create-text) (. self quit-x)
(. self quit-y)
:text "Quit: q"
:tag "sample"
:font (, "Arial" 12)
:fill "white"
:anchor (. tk W))
((. self canvas create-text) (. self r-x)
(. self r-y)
:text ((. "r: Z < {0:03d} > A" format) (. self gp r))
:tag "sample"
:font (, "Arial" 12)
:fill "white"
:anchor (. tk E))
((. self canvas create-text) (. self a-x)
(. self a-y)
:text ((. "a: X < {0:03d} > S" format) (. self gp a))
:tag "sample"
:font (, "Arial" 12)
:fill "white"
:anchor (. tk E))
((. self canvas create-text) (. self c-x)
(. self c-y)
:text ((. "c: C < {0:03d} > D" format) (. self gp c))
:tag "sample"
:font (, "Arial" 12)
:fill "white"
:anchor (. tk E))
((. self canvas create-text) (. self d-theta-x)
(. self d-theta-y)
:text ((. "d-theta: V < {0:03d} > F" format) (. self gp d-theta))
:tag "sample"
:font (, "Arial" 12)
:fill "white"
:anchor (. tk E)))
(defn delete [self]
"Delete the element with the sample tag"
((. self canvas delete) "sample"))
(defn loop [self]
;;Clear the previous screen
((. self delete))
;;Get new state and draw
((. self gp inc-phi))
((. self gp fetch-points))
((. self draw))
;;Execute loop after about 16ms
((. self window after) (. Simulator *ms*) (. self loop))))
(defn main []
(setv simulator (Simulator *width* *height*))
((. simulator loop))
((. simulator window mainloop))
0)
(when (= --name-- "__main__")
((. sys exit) (main)))
Create a batch file to call and execute the above geometric_pattern.gui module.
activate
the virtual environment so that the `hy``` command and hy module are in the path. Then use the
`hy``` command to run the geometric_pattern.gui module.
With the -m option, you can specify modules in a package in dot-separated notation.
geometric_pattern.bat
@ECHO OFF
SETLOCAL
SET THISDIR=%~dp0
SET PYTHONPATH=%THISDIR%;%PYTHONPATH%
SET ACTIVATE=%USERPROFILE%\.virtualenvs\venv_hy_geometric_pattern\Scripts\activate.bat
CALL %ACTIVATE%
hy -m geometric_pattern.gui
SET STATUS=%ERRORLEVEL%
CALL deactivate
IF %STATUS%==0 (
ECHO Done.
PAUSE
EXIT /b 0
) ELSE (
ECHO Error.
PAUSE
EXIT /b 1
)
Let's do it.
Yes. It may be fun to change the color and thickness of the path, or change it to another formula.
Recommended Posts