Skip to content

Commit

Permalink
First Upload
Browse files Browse the repository at this point in the history
  • Loading branch information
AsulconS committed Feb 8, 2024
1 parent c92452b commit c25ebac
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Build-specific ignores
__pycache__/
build/
dist/
*.spec
56 changes: 56 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import customtkinter as ctk

from plot import PlotFrame
from input import InputsFrame


class App(ctk.CTk):
def __init__(self):
super().__init__()
def _on_click_callback(event):
event.widget.focus_set()
self.bind_all('<Button-1>', lambda event: _on_click_callback(event))

def _on_key_press_callback(event):
if (event.char == '\r'):
self.focus_set()
self.bind('<KeyPress>', lambda event: _on_key_press_callback(event))

self.title('Second Order Dynamics Tool')
self.geometry('1366x768')
self.grid_rowconfigure((0, 2), weight=0)
self.grid_rowconfigure((1), weight=1)
self.grid_columnconfigure((0), weight=1)
self.grid_columnconfigure((1), weight=20)

self.title_bar = ctk.CTkLabel(master=self, text='Second Order\nDynamics', font=('Cascadia Code', 26))
self.title_bar.grid(row=0, column=0, padx=10, pady=10)

self.input_frame = InputsFrame(master=self)
self.plot_frame = PlotFrame(master=self, input_frame=self.input_frame)
self.input_frame.set_plot_callback(self.plot_frame.update_plot)
self.exit_button = ctk.CTkButton(master=self, text='Exit', font=('Cascadia Code', 16), command=self._exit_callback)

self.input_frame.grid(row=1, column=0, padx=10, pady=(10, 5), sticky='nsew')
self.plot_frame.grid(row=0, rowspan=2, column=1, padx=(5, 10), pady=(10, 5), sticky='nsew')
self.exit_button.grid(row=2, column=0, padx=10, pady=(5, 10), sticky='sew')

self.wm_protocol('WM_DELETE_WINDOW', self._quit)

self.input_frame.register_validators()
self.plot_frame.update_plot()


def _exit_callback(self):
self._quit()


def _quit(self):
self.withdraw()
self.quit()


if __name__ == '__main__':
ctk.set_appearance_mode('dark')
app = App()
app.mainloop()
40 changes: 40 additions & 0 deletions functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from math import modf


def map_range(value, from_, to):
return to[0] + (to[1] - to[0]) * ((value - from_[0]) / (from_[1] - from_[0]))


def default_x_function(t):
if t < 0.9:
return 0.6
elif t < 1.1:
return map_range(t, from_=(0.9, 1.1), to=(0.6, 1.0))
elif t < 2.4:
return 1.0
elif t < 2.5:
return map_range(t, from_=(2.4, 2.5), to=(1.0, 0.25))
elif t < 3.5:
return 0.25
elif t < 4.5:
return map_range(t, from_=(3.5, 4.5), to=(0.25, 0.8))
elif t < 5.0:
return map_range(t, from_=(4.5, 5.0), to=(0.8, 0.6))
else:
return 0.6


def spiky_x_function(t):
t_prime = modf(t)[0]
if t_prime < 0.5:
return map_range(t_prime, from_=(0.0, 0.5), to=(0.65, 0.75))
else:
return map_range(t_prime, from_=(0.5, 1.0), to=(0.75, 0.65))


def jitter_x_function(t):
t_prime = modf(15.0 * t)[0]
if t_prime < 0.5:
return map_range(t_prime, from_=(0.0, 0.5), to=(0.68, 0.72))
else:
return map_range(t_prime, from_=(0.5, 1.0), to=(0.72, 0.68))
114 changes: 114 additions & 0 deletions input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import numpy as np
import customtkinter as ctk

from re import match


class InputVarFrame(ctk.CTkFrame):
def __init__(self, master, name, default_val=0.0, slider_range=(0, 1), assignment_char=' =', font_size=26):
super().__init__(master)

self.grid_rowconfigure((0), weight=1)
self.grid_columnconfigure((0), weight=1)
self.grid_columnconfigure((1), weight=1)
self.grid_columnconfigure((2), weight=5)

self.slider_from, self.slider_to = slider_range
number_of_steps = (self.slider_to - self.slider_from) * 100
self.label = ctk.CTkLabel (master=self, text=f'{name}{assignment_char}', font=('Cascadia Code', font_size))
self.entry = ctk.CTkEntry (master=self, placeholder_text=f'{default_val}', font=('Cascadia Code', 16), justify='center', width=50)
self.slider = ctk.CTkSlider(master=self, from_=self.slider_from, to=self.slider_to, number_of_steps=number_of_steps, command=self._slider_modify_callback)

self.entry.insert(0, str(default_val))
self.slider.set(np.clip(default_val, self.slider_from, self.slider_to))

self.label.grid (row=0, column=0, padx=(20, 0), sticky='ew')
self.entry.grid (row=0, column=1, sticky='ew')
self.slider.grid(row=0, column=2, padx=(10, 20), sticky='ew')

self.cached_entry_content = '0.0'
self.entry.bind('<FocusIn>', lambda event: self._on_entry_focus_in_callback(event))
self.entry.bind('<FocusOut>', lambda event: self._on_entry_focus_out_callback(event))


def get_value(self):
return self.slider.get()


def set_plot_callback(self, plot_callback):
self.plot_callback = plot_callback


def register_validator(self):
entry_assert_command = self.register(self._entry_modify_callback)
self.entry.configure(validate='all', validatecommand=(entry_assert_command, '%d', '%V', '%P'))


def _on_entry_focus_in_callback(self, event):
self.cached_entry_content = self.entry.get()


def _on_entry_focus_out_callback(self, event):
entry_content = self.entry.get()
if (entry_content == '' or entry_content == '.'):
formatted_entry_val = float(self.cached_entry_content)
else:
formatted_entry_val = round(float(entry_content), 2)
formatted_entry_val = np.clip(formatted_entry_val, self.slider_from, self.slider_to)
formatted_entry = str(formatted_entry_val)
self.entry.delete(0, len(entry_content))
self.entry.insert(0, formatted_entry)


def _entry_modify_callback(self, action, reason, pending_mod):
if pending_mod == '' or pending_mod == '.':
return True

is_valid_match = match(r'^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$', pending_mod)
if is_valid_match is None:
return False

parsed_value = float(pending_mod)
parsed_value = np.clip(parsed_value, self.slider_from, self.slider_to)
self.slider.set(parsed_value)
self.plot_callback()
return True


def _slider_modify_callback(self, value):
self.entry.delete(0, len(self.entry.get()))
self.entry.insert(0, f'{round(value, 2)}')
self.plot_callback()


class InputsFrame(ctk.CTkFrame):
def __init__(self, master):
super().__init__(master)

self.grid_rowconfigure((0, 1, 2, 4, 5), weight=1)
self.grid_rowconfigure((3), weight=5)
self.grid_columnconfigure((0), weight=1)

self.f_var = InputVarFrame(master=self, name='f', default_val=1.0, slider_range=(0.01, 6.0))
self.z_var = InputVarFrame(master=self, name='ζ', default_val=0.5, slider_range=(0.0, 6.0))
self.r_var = InputVarFrame(master=self, name='r', default_val=2.0, slider_range=(-3.0, 3.0))
self.ts_var = InputVarFrame(master=self, name='Time Span', default_val=6.0, slider_range=(0.01, 10.0), assignment_char=':', font_size=20)

self.f_var.grid (row=0, column=0, padx=10, pady=(10, 5), sticky='nsew')
self.z_var.grid (row=1, column=0, padx=10, pady=( 5, 5), sticky='nsew')
self.r_var.grid (row=2, column=0, padx=10, pady=( 5, 5), sticky='nsew')
self.ts_var.grid(row=5, column=0, padx=10, pady=( 5, 10), sticky='nsew')


def set_plot_callback(self, plot_callback):
self.f_var.set_plot_callback(plot_callback)
self.z_var.set_plot_callback(plot_callback)
self.r_var.set_plot_callback(plot_callback)
self.ts_var.set_plot_callback(plot_callback)


def register_validators(self):
self.f_var.register_validator()
self.z_var.register_validator()
self.r_var.register_validator()
self.ts_var.register_validator()
94 changes: 94 additions & 0 deletions plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import numpy as np
import customtkinter as ctk
import matplotlib.pyplot as plt

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

from functions import *


class PlotFrame(ctk.CTkFrame):
def __init__(self, master, input_frame):
super().__init__(master)

self.input_frame = input_frame

self.grid_rowconfigure((0), weight=0)
self.grid_rowconfigure((1), weight=1)
self.grid_columnconfigure((0), weight=1)

master_color_dark = self.cget('fg_color')[1]
master_color_dark_name = master_color_dark.rstrip('0123456789')
master_color_dark_percent = int(master_color_dark[len(master_color_dark_name):])
master_color_dark_value = round((master_color_dark_percent / 100) * 255)
master_color_dark_value_hex = hex(master_color_dark_value)[2:]
master_color_dark_hex = f'#{master_color_dark_value_hex}{master_color_dark_value_hex}{master_color_dark_value_hex}'

self.fig, self.ax = plt.subplots(facecolor=master_color_dark_hex, dpi=100)
self.ax.set_facecolor(master_color_dark_hex)

self.ax.axhline(linewidth=2, color='white')
self.ax.axvline(linewidth=2, color='white')
self.ax.set_xlabel('Time', color='white', fontsize=14)
self.ax.set_ylabel('Position', color='white', fontsize=14)
self.ax.tick_params(direction='out', length=6, width=2, colors='white', grid_color='#444444', grid_alpha=0.5)

self.x_functions = {
'Default': default_x_function,
'Spiky': spiky_x_function,
'Jitter': jitter_x_function
}
self.last_plots = None

self.plot_selection = ctk.CTkComboBox(master=self, values=list(self.x_functions.keys()), state='readonly', font=('Cascadia Code', 16), command=self._combobox_callback)
self.plot_selection.grid(row=0, column=0, padx=40, pady=(20, 0), sticky='nw')
self.plot_selection.set('Default')

self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.get_tk_widget().grid(row=1, column=0, padx=10, pady=(0, 10), sticky='nsew')

self._combobox_callback('Default')


def set_x_function(self, x_function):
self.x_function = x_function


def update_plot(self):
if not self.last_plots is None:
for plot in self.last_plots: plot.remove()

f = self.input_frame.f_var.get_value()
z = self.input_frame.z_var.get_value()
r = self.input_frame.r_var.get_value()
ts = self.input_frame.ts_var.get_value()

dt = 0.01
T = np.arange(0.0, ts, dt)
X_vec = np.vectorize(self.x_function)
X = X_vec(T)

k1 = z / (np.pi * f)
k2 = 1.0 / ((2.0 * np.pi * f) * (2.0 * np.pi * f))
k3 = (r * z) / (2.0 * np.pi * f)

Y = np.zeros(shape=T.shape)
Y[0] = X[0]
yd = 0
for i in range(1, Y.size):
xd = (X[i] - X[i - 1]) / dt
stable_k2 = max(k2, 0.5*dt*dt + 0.5*dt*k1, dt*k1)
Y[i] = Y[i - 1] + dt * yd
yd = yd + dt * (X[i] + k3*xd - Y[i] - k1*yd) / stable_k2

plt.xlim(0.0, ts)
plt.ylim(0, 1.5)
plt.grid(visible=True, which='both')
self.last_plots = self.ax.plot(T, X, color='#8EB173', linewidth=2.5)
self.last_plots = self.last_plots + self.ax.plot(T, Y, color='#1F6AA5', linewidth=2.5)
self.canvas.draw()


def _combobox_callback(self, choice):
self.set_x_function(self.x_functions[choice])
self.update_plot()

0 comments on commit c25ebac

Please sign in to comment.