{ "cells": [ { "cell_type": "markdown", "id": "e4b00cf1", "metadata": {}, "source": [ "# A GUI for a 4-bar mechanism" ] }, { "cell_type": "markdown", "id": "63e01c77", "metadata": {}, "source": [ "## 1. the formulas" ] }, { "cell_type": "markdown", "id": "9fe2299f", "metadata": {}, "source": [ "We use sympy to produce the solution to drive a planar fourbar mechanisms." ] }, { "cell_type": "markdown", "id": "20e17a05", "metadata": {}, "source": [ "The crank $C$ of length $L$ and angle $t$ has coordinates $(L\\cos(t),L\\sin(t))$. " ] }, { "cell_type": "markdown", "id": "4831af50", "metadata": {}, "source": [ "The point $D(x,y)$ is at distance $r$ from $B(a,0)$.\n", "This gives the first equation defined by $f$:\n", "\n", "$$\n", " f = (x-a)^2 + y^2 - r^2 = 0.\n", "$$" ] }, { "cell_type": "markdown", "id": "8e6d5e9b", "metadata": {}, "source": [ "The point $D(x,y)$ is at distance $R$ from the crank $C$\n", "with coordinates $(L \\cos(t),L\\sin(t))$. This gives the second \n", "the equation defined by $g$: \n", "\n", "$$\n", " g = (x - L \\cos(t))^2 + (y - L \\sin(t))^2 - R^2 = 0.\n", "$$" ] }, { "cell_type": "markdown", "id": "553093da", "metadata": {}, "source": [ "Replacing $\\cos(t)$ and $\\sin(t)$ by $c$ and $s$ respectively, adding\n", "\n", "$$\n", " h = c^2 + s^2 - 1 = 0\n", "$$\n", "\n", "we obtain a system of algebraic equations\n", "in $(x,y)$ and with parameters $(a,r,R,L)$." ] }, { "cell_type": "code", "execution_count": 1, "id": "2923b5ae", "metadata": {}, "outputs": [], "source": [ "import sympy as sp\n", "from sympy import sin, cos" ] }, { "cell_type": "code", "execution_count": 2, "id": "e82287b6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(a, x, y, r, R, L, t, c, s)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.var('a,x,y,r,R,L,t,c,s')" ] }, { "cell_type": "code", "execution_count": 3, "id": "de443b05", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "f = -r**2 + y**2 + (-a + x)**2\n", "g = -R**2 + (-L*sin(t) + y)**2 + (-L*cos(t) + x)**2\n", "gcs = -R**2 + (-L*c + x)**2 + (-L*s + y)**2\n", "h = c**2 + s**2 - 1\n" ] } ], "source": [ "f = (x-a)**2 + y**2 - r**2;\n", "g = (x-L*cos(t))**2 + (y-L*sin(t))**2 - R**2;\n", "gcs = sp.Subs(g,(cos(t),sin(t)),(c,s)).doit()\n", "h = c**2 + s**2 - 1\n", "print('f =', f)\n", "print('g =', g)\n", "print('gcs =', gcs)\n", "print('h =', h)" ] }, { "cell_type": "code", "execution_count": 4, "id": "97afdd0b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "GroebnerBasis([L**2 - 2*L*c*x - 2*L*s*y - R**2 + x**2 + y**2, -L**3*c + 3*L**2*a + 2*L**2*c**2*x + 2*L**2*c*s*y - 2*L**2*x + L*R**2*c - 3*L*a**2*c - 6*L*a*s*y + 3*L*c*r**2 - 4*L*c*y**2 + 4*L*s*x*y - 3*R**2*a + 2*R**2*x + a**3 - a*r**2 + 4*a*y**2 - 2*r**2*x, L**3*c*s - 3*L**2*a*s + 2*L**2*c**3*y - 2*L**2*c**2*s*x - 2*L**2*c*y + 2*L**2*s*x - L*R**2*c*s + 3*L*a**2*c*s - 6*L*a*c**2*y + 6*L*a*y + 4*L*c**2*x*y - 3*L*c*r**2*s + 4*L*c*s*y**2 - 4*L*x*y + 3*R**2*a*s - 2*R**2*s*x - a**3*s + a*r**2*s - 4*a*s*y**2 + 2*r**2*s*x, L**2 - 2*L*c*x - 2*L*s*y - R**2 - a**2 + 2*a*x + r**2, L**4 - 4*L**3*a*c - 4*L**3*s*y - 2*L**2*R**2 + 4*L**2*a**2*c**2 + 2*L**2*a**2 + 8*L**2*a*c*s*y - 4*L**2*c**2*r**2 + 2*L**2*r**2 + 4*L**2*y**2 + 4*L*R**2*a*c + 4*L*R**2*s*y - 4*L*a**3*c - 4*L*a**2*s*y + 4*L*a*c*r**2 - 8*L*a*c*y**2 - 4*L*r**2*s*y + R**4 - 2*R**2*a**2 - 2*R**2*r**2 + a**4 - 2*a**2*r**2 + 4*a**2*y**2 + r**4, c**2 + s**2 - 1], x, y, r, s, a, c, L, R, domain='ZZ', order='lex')\n" ] } ], "source": [ "F = [f,gcs,h]\n", "# xsol0 = sp.solve(F,x)\n", "# This crashes: only zero dimensional Groebner bases are supported...\n", "# But then the results below are not really meaningful?\n", "gb = sp.groebner(F,order='lex')\n", "print(gb)" ] }, { "cell_type": "markdown", "id": "a4c2ed0e", "metadata": {}, "source": [ "## 2. a class to model a 4-bar mechanism" ] }, { "cell_type": "markdown", "id": "1ecafdca", "metadata": {}, "source": [ "An instance of the class ``FourBar`` stores the lengths of the bars and computes the coordinates of the joints that define the mechanism, for a given number of angles." ] }, { "cell_type": "code", "execution_count": 5, "id": "21ce2c2d", "metadata": {}, "outputs": [], "source": [ "from math import cos, sin, pi, sqrt" ] }, { "cell_type": "code", "execution_count": 6, "id": "7b545d6c", "metadata": {}, "outputs": [], "source": [ "class FourBar(object):\n", " \"\"\"\n", " A 4-bar mechanism is determined by five parameters:\n", " (1) a : the length of the horizontal bar,\n", " (2) L : the length of the crank,\n", " (3) R : the length of the bar attached to the crank,\n", " (4) r : the length of the bar to the right,\n", " (5) b : the length of the bar to the coupler.\n", " The mechanism is driven by a crank, with angle t,\n", " and we use c = cos(t) and s = sin(t).\n", " The value of t determines the state of the mechanism.\n", " \"\"\"\n", "\n", " def __init__(self, a=100, L=50, R=125, r=125, b=125, t=pi/2):\n", " \"\"\"\n", " Initializes a 4-bar mechanism with the 5 given lengths\n", " and sets the state of the 4-bar to the angle t.\n", " Default values are for the Chebyshev mechanism.\n", " \"\"\"\n", " self.flat = a # (0,a) is joint\n", " self.crank = L # length of the crank\n", " self.top = R # length of bar attached to crank\n", " self.right = r # length of the right bar\n", " self.coupler = b # length of bar to the coupler\n", " self.angle = t # state of the 4-bar\n", "\n", " def z1(self):\n", " \"\"\"\n", " Returns the value of L^2+a^2-2*a*L*c.\n", " \"\"\"\n", " a = self.flat\n", " L = self.crank\n", " c = cos(self.angle)\n", " v = L**2 + a**2 - 2.0*a*L*c\n", " return v\n", "\n", " def z2(self):\n", " \"\"\"\n", " Returns the value of -L*R^2*s+L^3*s+L*s*r^2+a^2*L*s-2*L^2*s*c*a.\n", " \"\"\"\n", " a = self.flat\n", " L = self.crank\n", " R = self.top\n", " r = self.right\n", " c = cos(self.angle)\n", " s = sin(self.angle)\n", " v = -L*R**2*s+L**3*s+L*s*r**2+a**2*L*s-2*L**2*s*c*a\n", " return v\n", "\n", " def z3(self):\n", " \"\"\"\n", " Returns the value of the square root of the discriminant:\n", " sqrt(-2*L^2*R^2*s^2*a^2-2*L^2*R^2*s^2*r^2+2*a^4*R^2-a^2*r^4\n", " +L^6*s^2-L^2*R^4+2*L^4*R^2+2*L^4*r^2-L^2*r^4-7*L^4*a^2\n", " -7*L^2*a^4-L^6-a^6+6*L^4*s^2*a^2+4*L^3*R^2*s^2*c*a\n", " -4*L^5*s^2*c*a-2*L^2*s^2*r^2*a^2-2*L^4*R^2*s^2+L^2*s^2*r^4\n", " +5*a^4*L^2*s^2+2*L^2*r^2*R^2+6*L^5*a*c+4*L^2*a^2*R^2\n", " +4*L^2*r^2*a^2+20*L^3*c*a^3+2*a^2*r^2*R^2+4*L^3*s^2*r^2*c*a\n", " -12*a^3*L^3*s^2*c+4*L^4*s^2*c^2*a^2-8*L^3*a*R^2*c-8*L^3*a*r^2*c\n", " -8*a^3*R^2*L*c-8*a^3*r^2*L*c-a^2*R^4+2*a^4*r^2+6*a^5*L*c\n", " -8*a^2*L^4*c^2-8*a^4*L^2*c^2+2*a*L*c*R^4+2*a*L*c*r^4\n", " +8*a^2*L^2*c^2*R^2+8*a^2*L^2*c^2*r^2-2*L^4*s^2*r^2\n", " +L^2*R^4*s^2-4*a*L*c*r^2*R^2)\n", " If the argument of the sqrt is negative, zero is returned.\n", " \"\"\"\n", " a = self.flat\n", " L = self.crank\n", " c = cos(self.angle)\n", " s = sin(self.angle)\n", " R = self.top\n", " r = self.right\n", " v = -2.0*L**2*R**2*s**2*a**2-2*L**2*R**2*s**2*r**2+2*a**4*R**2 \\\n", " -a**2*r**4+L**6*s**2-L**2*R**4+2*L**4*R**2+2*L**4*r**2-L**2*r**4 \\\n", " -7*L**4*a**2-7*L**2*a**4-L**6-a**6+6*L**4*s**2*a**2 \\\n", " +4*L**3*R**2*s**2*c*a-4*L**5*s**2*c*a-2*L**2*s**2*r**2*a**2 \\\n", " -2*L**4*R**2*s**2+L**2*s**2*r**4 +5*a**4*L**2*s**2 \\\n", " +2*L**2*r**2*R**2+6*L**5*a*c+4*L**2*a**2*R**2 \\\n", " +4*L**2*r**2*a**2+20*L**3*c*a**3+2*a**2*r**2*R**2 \\\n", " +4*L**3*s**2*r**2*c*a-12*a**3*L**3*s**2*c+4*L**4*s**2*c**2*a**2 \\\n", " -8*L**3*a*R**2*c-8*L**3*a*r**2*c-8*a**3*R**2*L*c-8*a**3*r**2*L*c \\\n", " -a**2*R**4+2*a**4*r**2+6*a**5*L*c-8*a**2*L**4*c**2 \\\n", " -8*a**4*L**2*c**2+2*a*L*c*R**4+2*a*L*c*r**4 \\\n", " +8*a**2*L**2*c**2*R**2+8*a**2*L**2*c**2*r**2-2*L**4*s**2*r**2 \\\n", " +L**2*R**4*s**2-4*a*L*c*r**2*R**2\n", " if v < 0:\n", " return 0\n", " else:\n", " return sqrt(v)\n", "\n", " def y1p(self):\n", " \"\"\"\n", " Returns the value of (1/2)*(z2+z3)/z1.\n", " \"\"\"\n", " v = (self.z2()+self.z3())/(2*self.z1())\n", " return v\n", "\n", " def y1m(self):\n", " \"\"\"\n", " Returns the value of (1/2)*(z2-z3)/z1.\n", " \"\"\"\n", " v = (self.z2()-self.z3())/(2*self.z1())\n", " return v\n", "\n", " def x(self, y):\n", " \"\"\"\n", " Returns the value of (1/2)*(-a^2+r^2+L^2-R^2-2*y*L*s)/(-a+L*c).\n", " \"\"\"\n", " a = self.flat\n", " L = self.crank\n", " c = cos(self.angle)\n", " s = sin(self.angle)\n", " R = self.top\n", " r = self.right\n", " x_den = 2*(-a+L*c)\n", " x_num = -a**2+r**2+L**2-R**2-2*y*L*s\n", " v = x_num/x_den\n", " return v\n", "\n", " def connector_point(self):\n", " \"\"\"\n", " Returns coordinates of connector point.\n", " For complex values (0, 0) is returned.\n", " \"\"\"\n", " try:\n", " y1pv = self.y1p()\n", " x1pv = self.x(y1pv)\n", " return (x1pv, y1pv)\n", " except:\n", " return (0, 0)\n", "\n", " def coupler_point(self):\n", " \"\"\"\n", " Returns the coordinates of the coupler point.\n", " \"\"\"\n", " t = self.angle\n", " L = self.crank\n", " ax = L*cos(t)\n", " ay = L*sin(t)\n", " p = self.connector_point()\n", " dx = p[0] - ax\n", " dy = p[1] - ay\n", " b = self.coupler\n", " if dx == 0:\n", " x = ax\n", " y = p[1] + b\n", " else:\n", " v = sqrt(dx*dx + dy*dy)\n", " x = p[0] + b*dx/v\n", " y = p[1] + b*dy/v\n", " return (x, y)\n", "\n", " def joints(self):\n", " \"\"\"\n", " Returns the list of the coordinates of the\n", " five joints that define the mechanism.\n", " \"\"\"\n", " L = [(0, 0), (self.flat, 0)]\n", " t = self.angle\n", " L.append((self.crank*cos(t), self.crank*sin(t)))\n", " L.append(self.connector_point())\n", " L.append(self.coupler_point())\n", " return L\n", "\n", " def print_joints(self):\n", " \"\"\"\n", " Prints the coordinates for the joints.\n", " \"\"\"\n", " L = self.joints()\n", " s = \"%.2f \" % self.angle\n", " for p in L:\n", " s = s + \"(%5.1f, %5.1f)\" % p\n", " print(s)\n", "\n", " def track_path(self, nbr):\n", " \"\"\"\n", " Computes coordinates of points along a path\n", " tracked by the coupler point, for a number of samples.\n", " \"\"\"\n", " h = 2*pi/nbr\n", " for i in range(0, nbr):\n", " self.print_joints()\n", " self.angle = self.angle + h\n", " if self.angle > 2*pi:\n", " self.angle = self.angle - 2*pi" ] }, { "cell_type": "code", "execution_count": 7, "id": "cc7dfee1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.57 ( 0.0, 0.0)(100.0, 0.0)( 0.0, 50.0)(100.0, 125.0)(200.0, 200.0)\n", "1.88 ( 0.0, 0.0)(100.0, 0.0)(-15.5, 47.6)( 83.5, 123.9)(182.5, 200.3)\n", "2.20 ( 0.0, 0.0)(100.0, 0.0)(-29.4, 40.5)( 66.6, 120.5)(162.7, 200.5)\n", "2.51 ( 0.0, 0.0)(100.0, 0.0)(-40.5, 29.4)( 50.7, 114.9)(141.9, 200.4)\n", "2.83 ( 0.0, 0.0)(100.0, 0.0)(-47.6, 15.5)( 36.7, 107.8)(121.0, 200.1)\n", "3.14 ( 0.0, 0.0)(100.0, 0.0)(-50.0, 0.0)( 25.0, 100.0)(100.0, 200.0)\n", "3.46 ( 0.0, 0.0)(100.0, 0.0)(-47.6, -15.5)( 15.7, 92.3)( 79.0, 200.1)\n", "3.77 ( 0.0, 0.0)(100.0, 0.0)(-40.5, -29.4)( 8.8, 85.5)( 58.1, 200.4)\n", "4.08 ( 0.0, 0.0)(100.0, 0.0)(-29.4, -40.5)( 4.0, 80.0)( 37.3, 200.5)\n", "4.40 ( 0.0, 0.0)(100.0, 0.0)(-15.5, -47.6)( 1.0, 76.4)( 17.5, 200.3)\n", "4.71 ( 0.0, 0.0)(100.0, 0.0)( -0.0, -50.0)( -0.0, 75.0)( -0.0, 200.0)\n", "5.03 ( 0.0, 0.0)(100.0, 0.0)( 15.5, -47.6)( 1.2, 76.6)(-13.0, 200.8)\n", "5.34 ( 0.0, 0.0)(100.0, 0.0)( 29.4, -40.5)( 5.9, 82.3)(-17.5, 205.1)\n", "5.65 ( 0.0, 0.0)(100.0, 0.0)( 40.5, -29.4)( 16.9, 93.4)( -6.7, 216.1)\n", "5.97 ( 0.0, 0.0)(100.0, 0.0)( 47.6, -15.5)( 39.3, 109.3)( 31.1, 234.0)\n", "6.28 ( 0.0, 0.0)(100.0, 0.0)( 50.0, -0.0)( 75.0, 122.5)(100.0, 244.9)\n", "0.31 ( 0.0, 0.0)(100.0, 0.0)( 47.6, 15.5)(108.2, 124.7)(168.9, 234.0)\n", "0.63 ( 0.0, 0.0)(100.0, 0.0)( 40.5, 29.4)(123.6, 122.8)(206.7, 216.1)\n", "0.94 ( 0.0, 0.0)(100.0, 0.0)( 29.4, 40.5)(123.4, 122.8)(217.5, 205.1)\n", "1.26 ( 0.0, 0.0)(100.0, 0.0)( 15.5, 47.6)(114.2, 124.2)(213.0, 200.8)\n" ] } ], "source": [ "fbr = FourBar()\n", "fbr.track_path(20)" ] }, { "cell_type": "markdown", "id": "725b9a34", "metadata": {}, "source": [ "## 3. defining the GUI" ] }, { "cell_type": "markdown", "id": "7042476f", "metadata": {}, "source": [ "In the design of the GUI, we define its widgets.\n", "\n", "1. The GUI consists of one canvas to draw a 4-bar mechanism.\n", "\n", "2. A 4-bar mechanism consists of four rigid bars. \n", "\n", " 1. The first bar, the crank, drives the mechanism. \n", " The first two scales at the left regulate the length of the crank and the angle of the crank.\n", "\n", " 2. The horizontal scale below the canvas determine the position of the\n", " rightmost bottom joint, at the start of the right bar.\n", "\n", " 3. The bar attached to the crank connects to the right bar.\n", " six scales and six corresponding labels. Five of the six scales\n", " determine the lenghts of the bars of the mechanims." ] }, { "cell_type": "code", "execution_count": 8, "id": "6a6580cf", "metadata": {}, "outputs": [], "source": [ "from tkinter import Tk, Canvas, Entry, Label, Scale\n", "from tkinter import IntVar, DoubleVar, Button, Checkbutton\n", "from tkinter import W, E, LEFT, RIGHT, INSERT, END, ALL\n", "from fourbar import FourBar" ] }, { "cell_type": "code", "execution_count": 9, "id": "a309f376", "metadata": {}, "outputs": [], "source": [ "class FourBarGUI(object):\n", " \"\"\"\n", " GUI to model a 4-bar mechanism.\n", " \"\"\"\n", " def __init__(self, wdw, r, c):\n", " \"\"\"\n", " Determines layout of the canvas,\n", " number of rows and colums is r and c.\n", " \"\"\"\n", " wdw.title('a 4-bar mechanism')\n", " self.fbr = FourBar()\n", " self.rows = r\n", " self.cols = c\n", " self.ox = c/3\n", " self.oy = 3*r/4\n", " # print \"A =\" , (self.ox, self.oy)\n", " self.togo = False\n", " # the canvas and start, stop, and clear buttons\n", " self.c = Canvas(wdw, width=self.cols, height=self.rows, bg='green')\n", " self.c.grid(row=1, column=2, columnspan=2)\n", " self.startbut = Button(wdw, text='start', command = self.start)\n", " self.startbut.grid(row=3, column=2, sticky=W+E)\n", " self.stopbut = Button(wdw, text='stop', command = self.stop)\n", " self.stopbut.grid(row=3, column=3, sticky=W+E)\n", " self.clearbut = Button(wdw, text='clear', command = self.clear)\n", " self.clearbut.grid(row=3, column=4, columnspan=3, sticky=W+E)\n", " # the length of the crank\n", " self.crank_lbl = Label(wdw, text='crank', justify=LEFT)\n", " self.crank_lbl.grid(row=0, column=0)\n", " self.crank_bar = IntVar()\n", " self.L = Scale(wdw, orient='vertical', from_=0, to=self.rows/2, \\\n", " tickinterval=20, resolution=1, length=self.rows, \\\n", " variable=self.crank_bar, command=self.draw_mechanism)\n", " self.L.set(self.fbr.crank)\n", " self.L.grid(row=1, column=0)\n", " # the angle that drives the crank\n", " self.angle_lbl = Label(wdw, text='angle', justify=LEFT)\n", " self.angle_lbl.grid(row=0, column=1)\n", " self.angle = DoubleVar()\n", " self.t = Scale(wdw, orient='vertical', from_=0, to=6.30, \\\n", " tickinterval=0.30, resolution=0.01, length=self.rows, \\\n", " variable=self.angle, command=self.draw_mechanism)\n", " self.t.grid(row=1, column=1)\n", " self.angle.set(self.fbr.angle)\n", " # the bar at the right\n", " self.right_bar_lbl = Label(wdw, text='right bar', justify=LEFT)\n", " self.right_bar_lbl.grid(row=0, column=4)\n", " self.right_bar = IntVar()\n", " self.r = Scale(wdw, orient='vertical', from_=0, to=self.rows/2, \\\n", " tickinterval=20, resolution=1, length=self.rows, \\\n", " variable=self.right_bar, command=self.draw_mechanism)\n", " self.r.grid(row=1, column=4)\n", " self.right_bar.set(self.fbr.right)\n", " # the top bar attached to the crank\n", " self.top_bar_lbl = Label(wdw, text='top bar', justify=LEFT)\n", " self.top_bar_lbl.grid(row=0, column=5)\n", " self.r_top_bar = IntVar()\n", " self.R = Scale(wdw, orient='vertical', from_=0, to=self.rows/2, \\\n", " tickinterval=20, resolution=1, length=self.rows, \\\n", " variable=self.r_top_bar, command=self.draw_mechanism)\n", " self.R.grid(row=1, column=5)\n", " self.r_top_bar.set(self.fbr.top)\n", " # the scale for the coupler bar\n", " self.coupler_bar_lbl = Label(wdw, text='coupler', justify=LEFT)\n", " self.coupler_bar_lbl.grid(row=0, column=6)\n", " self.coupler_bar = IntVar()\n", " self.cpl = Scale(wdw, orient='vertical', from_=0, to=self.rows/2, \\\n", " tickinterval=20, resolution=1, length=self.rows, \\\n", " variable=self.coupler_bar, command=self.draw_mechanism)\n", " self.cpl.grid(row=1, column=6)\n", " self.coupler_bar.set(self.fbr.coupler)\n", " # the horizontal bottom bar\n", " self.flat_lbl = Label(wdw, text='right joint', justify=RIGHT)\n", " self.flat_lbl.grid(row=2, column=1)\n", " self.flat = IntVar()\n", " self.f = Scale(wdw, orient='horizontal', from_=0, to=self.rows/2, \\\n", " tickinterval=50, resolution=1, length=self.cols, \\\n", " variable=self.flat, command=self.draw_mechanism)\n", " self.f.grid(row=2, column=2, columnspan=2)\n", " self.flat.set(self.fbr.flat)\n", " # coordinates of the coupler point appear on top\n", " self.ex = Entry(wdw) # for x value\n", " self.ex.grid(row=0, column=2)\n", " self.ex.insert(INSERT, \"x = \")\n", " self.ey = Entry(wdw) # for y value\n", " self.ey.grid(row=0, column=3)\n", " self.ey.insert(INSERT,\"y = \")\n", " # check button for drawing of coupler curve\n", " self.curve = IntVar()\n", " self.cb = Checkbutton(wdw, text='coupler', \\\n", " variable=self.curve, onvalue=1, offvalue=0)\n", " self.curve.set(1)\n", " self.cb.grid(row=3, column=0)\n", " # draw the mechanism on canvas\n", " self.draw_mechanism(0)\n", "\n", " def update_values(self):\n", " \"\"\"\n", " Takes all values of the scales and updates\n", " the data attributes of self.fbr.\n", " \"\"\"\n", " self.fbr.flat = self.flat.get()\n", " self.fbr.crank = self.crank_bar.get()\n", " self.fbr.top = self.r_top_bar.get()\n", " self.fbr.right = self.right_bar.get()\n", " self.fbr.coupler = self.coupler_bar.get()\n", " self.fbr.angle = self.angle.get()\n", " #self.fbr.print_joints()\n", "\n", " def draw_coupler_point(self, p):\n", " \"\"\"\n", " Draws coupler point with coordinates in p\n", " if the curve checkbox is on.\n", " Note that the previous values for the coordinates\n", " of the coupler point are stored in the entry fields.\n", " \"\"\"\n", " if self.curve.get() == 1:\n", " px = self.ox + p[0]\n", " py = self.oy - p[1]\n", " eqx = self.ex.get()\n", " Lx = eqx.split('=')\n", " if Lx[1] == ' ':\n", " qx = 0.0\n", " else:\n", " qx = float(Lx[1])\n", " eqy = self.ey.get()\n", " Ly = eqy.split('=')\n", " if Ly[1] == ' ':\n", " qy = 0.0\n", " else:\n", " qy = float(Ly[1])\n", " if (qx != 0.0) and (qy != 0.0):\n", " qx = self.ox + qx\n", " qy = self.oy - qy\n", " self.c.create_line(qx, qy, px, py, width=1)\n", "\n", " def fill_entries(self, p):\n", " \"\"\"\n", " Fills the entry fields with the coordinates\n", " of the coupler point in p.\n", " \"\"\"\n", " sx = 'x = %f' % p[0]\n", " sy = 'y = %f' % p[1]\n", " self.ex.delete(0, END)\n", " self.ex.insert(INSERT, sx)\n", " self.ey.delete(0, END)\n", " self.ey.insert(INSERT, sy)\n", "\n", " def draw_link(self, p, q, s):\n", " \"\"\"\n", " Draws the link from point with coordinates in p\n", " to the point with coordinates in q, using s as tag.\n", " \"\"\"\n", " self.c.delete(s)\n", " px = self.ox + p[0]\n", " py = self.oy - p[1]\n", " qx = self.ox + q[0]\n", " qy = self.oy - q[1]\n", " self.c.create_line(px, py, qx, qy, width=2, tags=s)\n", "\n", " def draw_mechanism(self, v):\n", " \"\"\"\n", " Fills the canvas with the current model\n", " of the planar 4-bar mechanism.\n", " Because this command is called by the sliders,\n", " the argument v is needed but not used.\n", " \"\"\"\n", " self.update_values()\n", " L = self.fbr.joints()\n", " for i in range(0, len(L)):\n", " p = L[i]\n", " px = self.ox + p[0]\n", " py = self.oy - p[1]\n", " sj = 'joint%d' % i\n", " self.c.delete(sj)\n", " self.c.create_oval(px-6, py-6, px+6, py+6, width=1, \\\n", " outline='black', fill='red', tags=sj)\n", " self.draw_link(L[0], L[2], 'link0')\n", " self.draw_link(L[1], L[3], 'link1')\n", " self.draw_link(L[2], L[3], 'link2')\n", " self.draw_link(L[2], L[4], 'link3')\n", " self.draw_coupler_point(L[4])\n", " self.fill_entries(L[4])\n", "\n", " def start(self):\n", " \"\"\"\n", " Starts the animation, adding 0.01 to angle.\n", " \"\"\"\n", " self.togo = True\n", " while self.togo:\n", " theta = self.angle.get()\n", " theta = theta + 0.01\n", " if theta > 6.28:\n", " theta = 0\n", " self.angle.set(theta)\n", " self.draw_mechanism(0)\n", " self.c.update()\n", "\n", " def stop(self):\n", " \"\"\"\n", " Stops the animation.\n", " \"\"\"\n", " self.togo = False\n", "\n", " def clear(self):\n", " \"\"\"\n", " Clears the canvas.\n", " \"\"\"\n", " self.c.delete(ALL)" ] }, { "cell_type": "markdown", "id": "e628cffc", "metadata": {}, "source": [ "The next cell launches the GUI, which will pop up a new window." ] }, { "cell_type": "code", "execution_count": 11, "id": "128a3113", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Exception in Tkinter callback\n", "Traceback (most recent call last):\n", " File \"C:\\Users\\jan\\miniconda3\\lib\\tkinter\\__init__.py\", line 1921, in __call__\n", " return self.func(*args)\n", " File \"C:\\Users\\jan\\AppData\\Local\\Temp\\ipykernel_22488\\3567148560.py\", line 196, in start\n", " self.draw_mechanism(0)\n", " File \"C:\\Users\\jan\\AppData\\Local\\Temp\\ipykernel_22488\\3567148560.py\", line 175, in draw_mechanism\n", " self.c.delete(sj)\n", " File \"C:\\Users\\jan\\miniconda3\\lib\\tkinter\\__init__.py\", line 2852, in delete\n", " self.tk.call((self._w, 'delete') + args)\n", "_tkinter.TclError: invalid command name \".!canvas\"\n" ] } ], "source": [ "top = Tk()\n", "r = 400\n", "c = 600\n", "dims = \"%dx%d\" % (r, c)\n", "top.geometry(dims)\n", "FourBarGUI(top, r, c)\n", "top.mainloop()" ] }, { "cell_type": "code", "execution_count": null, "id": "656b2d37-e0bf-4794-acfb-e3b9fcd5b459", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.10" } }, "nbformat": 4, "nbformat_minor": 5 }