# -*- coding: utf-8 -*- # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # gtk imports import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GObject, GLib # math/plot imports from matplotlib.backends.backend_gtk3agg import ( FigureCanvasGTK3Agg as FigureCanvas) from matplotlib.figure import Figure import numpy as np import math import sys class spirographs: def __init__(self): self.bigRadius = 12 self.smallRadius = 5 self.distance = 4 self.highestTheta = (np.lcm(self.smallRadius, self.bigRadius)/self.bigRadius) * 2 * math.pi self.stepSize = self.highestTheta / 4096 # update initial slider positions self.bigRadiusAdjustment = builder.get_object('bigRadiusAdjustment') self.smallRadiusAdjustment = builder.get_object('smallRadiusAdjustment') self.distanceAdjustment = builder.get_object('distanceAdjustment') self.bigRadiusEntry = builder.get_object('bigRadiusEntry') self.smallRadiusEntry = builder.get_object('smallRadiusEntry') self.distanceEntry = builder.get_object('distanceEntry') # set the initial values to a neat shape self.bigRadiusAdjustment.set_value(self.bigRadius) self.smallRadiusAdjustment.set_value(self.smallRadius) self.distanceAdjustment.set_value(self.distance) self.bigRadiusEntry.set_text(str(self.bigRadius)) self.smallRadiusEntry.set_text(str(self.smallRadius)) self.distanceEntry.set_text(str(self.distance)) # try and set the step size?! self.bigRadiusAdjustment.set_step_increment(0.5) self.smallRadiusAdjustment.set_step_increment(0.5) self.distanceAdjustment.set_step_increment(0.5) # create initial plot self.recalcPoints() self.showPlot() def calcEpiX(self, theta): return ((self.bigRadius + self.smallRadius) * math.cos(theta)) - (self.distance * math.cos(((self.bigRadius + self.smallRadius)/(self.smallRadius))*theta)) def calcEpiY(self, theta): return ((self.bigRadius + self.smallRadius) * math.sin(theta)) - (self.distance * math.sin(((self.bigRadius + self.smallRadius)/(self.smallRadius))*theta)) def calcHypoX(self, theta): return ((self.bigRadius - self.smallRadius) * math.cos(theta)) + (self.distance * math.cos(((self.bigRadius - self.smallRadius)/(self.smallRadius))*theta)) def calcHypoY(self, theta): return ((self.bigRadius - self.smallRadius) * math.sin(theta)) - (self.distance * math.sin(((self.bigRadius - self.smallRadius)/(self.smallRadius))*theta)) def onDestroy(self, widget): Gtk.main_quit() return def bigRadiusAdjustment_value_changed_cb(self, widget): self.bigRadius = widget.get_value() self.bigRadiusEntry.set_text(str(self.bigRadius)) self.recalcPoints() self.updatePlot() def bigRadiusEntry_changed_cb(self, widget): self.bigRadius = float(widget.get_text()) self.bigRadiusAdjustment.set_value(self.bigRadius) def smallRadiusAdjustment_value_changed_cb(self, widget): self.smallRadius = widget.get_value() self.smallRadiusEntry.set_text(str(self.smallRadius)) self.recalcPoints() self.updatePlot() def smallRadiusEntry_changed_cb(self, widget): self.smallRadius = float(widget.get_text()) self.smallRadiusAdjustment.set_value(self.smallRadius) def distanceAdjustment_value_changed_cb(self, widget): self.distance = widget.get_value() self.distanceEntry.set_text(str(self.distance)) self.recalcPoints() self.updatePlot() def distanceEntry_changed_cb(self, widget): self.distance = float(widget.get_text()) self.distanceAdjustment.set_value(self.distance) def recalcPoints(self): self.epiX = np.array([self.calcEpiX(i) for i in np.arange(0, self.highestTheta, self.stepSize)]) self.epiY = np.array([self.calcEpiY(i) for i in np.arange(0, self.highestTheta, self.stepSize)]) self.hypoX = np.array([self.calcHypoX(i) for i in np.arange(0, self.highestTheta, self.stepSize)]) self.hypoY = np.array([self.calcHypoY(i) for i in np.arange(0, self.highestTheta, self.stepSize)]) def showPlot(self): viewport = builder.get_object('plotViewport') self.plotFigure = Figure(figsize=(5, 8), dpi=100) self.epiPlot, self.hypoPlot = self.plotFigure.subplots(1, 2) self.epiPlot.set_title(f"Epitrochoid of {self.bigRadius}, {self.smallRadius}, {self.distance}") self.epiPlot.plot(self.epiX, self.epiY) self.hypoPlot.set_title(f"Hypotrochoid of {self.bigRadius}, {self.smallRadius}, {self.distance}") self.hypoPlot.plot(self.hypoX, self.hypoY) self.canvas = FigureCanvas(self.plotFigure) self.canvas.set_size_request(800, 600) viewport.add(self.canvas) def updatePlot(self): self.epiPlot.clear() self.hypoPlot.clear() self.epiPlot.set_title(f"Epitrochoid of {self.bigRadius}, {self.smallRadius}, {self.distance}") self.hypoPlot.set_title(f"Hypotrochoid of {self.bigRadius}, {self.smallRadius}, {self.distance}") self.epiPlot.plot(self.epiX, self.epiY) self.hypoPlot.plot(self.hypoX, self.hypoY) self.canvas.draw_idle() builder = Gtk.Builder() builder.add_from_file("spirographs.glade") sp = spirographs() builder.connect_signals(sp) window = builder.get_object("spWindow") window.show_all() Gtk.main()