Genauere Zeitnahme in Windows (Software)
Leider gibt es wohl viele Windows-Rechner, bei denen die Python-Funktion time.time() nur recht ungenaue Werte liefert, da sie nicht im Milli- oder Mikrosekundentakt wie unter Linux aktualisiert wird, sondern nur ungefähr 60 Mal pro Sekunde. Da sich damit schwerlich die zwischen zwei Rechenschritten verstrichene Zeit messen lässt, verwendet das Programm nun für Zeitmessungen die mit Python 3.3 neu eingeführte, ungleich präzisere Funktion time.perf_counter(), die selbst unter Windows brauchbar ist.
Download:
ZIP-Archiv_XQGMILX91.zip
# Interaktive Physiksimulation mit Massen und Federn # 2016-12-22 Martin Vogel, Hochschule Bochum # cc-by-sa-3.0 https://creativecommons.org/licenses/by-sa/3.0/de/ # Fragen und Diskussion: # http://bauforum.wirklichewelt.de/index.php?nr=11343 from tkinter import * from math import hypot,atan2,sin,cos from random import randint from time import perf_counter, sleep from threading import Thread, Lock # Das Hauptfenster des Programms wird erzeugt und der TCL-Interpreter # geladen. Alle weiteren davon abhängigen Fenster werden mittels Toplevel() # erzeugt. T = Tk() T.title("Massen und Federn (Hilfe mit F1)") # Die Zeichenfläche Canvas soll sich Größenänderungen des Hauptfensters # anpassen können. C = Canvas(T,width=800,height=600,bg="white") C.pack(expand="yes",fill="both") # Kurze Erläuterung der Programmfunktionen: def Hilfe(event): messagebox.showinfo("Hilfe", "Massen und Federn", detail=" Dieses physikalische Modell besteht aus blauen Lagerpunkten, roten Massepunkten und grauen Stabfedern. Die Lagerpunkte und die Massepunkte können mit der Maus verschoben werden. Die Federn folgen dieser Bewegung. nn Neue Massepunkte und Lagerpunkte lassen sich mit den Tasten „M“ und „L“ sowie mit der rechten Maustaste erzeugen. Massepunkte werden mittels einer Feder mit dem nächstliegenden verschieblichen Punkt verbunden, wenn die Gravitation ungleich null ist. Zum schnellen Einschalten der Schwerelosigkeit dient der Button „Antigravity“. nn Neue Federn werden durch zweimaliges Drücken der rechten Maustaste oder der Taste „F“ jeweils am Start- und am Zielobjekt angebunden. Unerwünschte Druck- oder Zugspannungen in den Federn, die während des Konstruierens entstehen können, lassen sich mit dem Button „Entspannen“ auf null setzen. nn Bei hohen Federsteifigkeiten und geringen Kugelmassen neigt das Modell dazu, in dramatischer Weise aufzuschwingen. Der Dämpfungsregler sollte daher niemals auf einem zu kleinen Wert stehen. nn Das Löschen von Objekten ist noch nicht implementiert. Auch das Laden und Speichern von Modellen und Reglerstellungen wird der geneigten Leserin und dem geneigten Leser des Python-3-Quelltextes dieses Programms überlassen. nn Viel Spaß damit! nn Martin Vogel, Hochschule Bochum, im Dezember 2016") class C_Einstellungen: """ Einige allgemeine Einstellungen, die zu experimentellen Zwecken variiert werden können.""" Gravitation = 9.81 # Die Einheit der Gravitationsbeschleunigung ist hier Hektopixel # pro Quadratsekunde. Federsteifigkeit = 100 # 100 Krafteinheiten werden benötigt, um die Länge einer Feder # um das Maß ihrer Länge im entspannten Zustand zu verändern. Masse = 5 # Die Masse der roten „Kugeln“. Dämpfung = 5 # Bei einer zu geringen Dämpfung wird die Simulation instabil. # Bei hohen Federsteifigkeiten und geringen Massen muss unbedingt # eine stärkere Dämpfung eingestellt werden. # todo: Die ganze Krampferei mit der Dämpfung gegen Aufschaukeln # könnte man sich sparen, wenn regelmäßig die aktuelle Gesamtenergie # des Systems ermittelt würde und bei Abdriften alle Geschwindigkeiten # um einen Korrekturfaktor reduziert würden. Zeige_Federkräfte = False # Numerische Anzeige der Stabkräfte Zeige_Auflagerkräfte = False # Anzeige der Auflagerkräfte Zeige_Spur = False # Aufzeichnung der Massepunktbewegung PPS = 240 # Physikschritte pro Sekunde. Dieser Wert passt sich # dynamisch an die Möglichkeiten der CPU an. Leider unterstützt # tkinter nur einen Thread, sodass es nicht möglich ist, die # Grafikausgabe zu parallelisieren. Für die Berechnungen selbst # wird jedoch Multithreading eingesetzt. # Eine Instanz dieser Klasse speichert unsere ganzen Programmeinstellungen Einstellungen = C_Einstellungen() class Feder: # Feder von Objekt1 nach Objekt2 def __init__(self,Objekt1,Objekt2,k=Einstellungen.Federsteifigkeit): # Start- und Zielpunkt self.x1, self.y1 = Objekt1.x, Objekt1.y self.x2, self.y2 = Objekt2.x, Objekt2.y # Federkonstante k (könnte auch Federsteifigkeit D heißen) self.k = k # aktuelle Länge self.l = hypot(self.x2-self.x1,self.y2-self.y1) # Anzeige der Federkraft (None oder Canvas-ID des Textes) self.Kraftanzeige = None # Weil an diese Feder zwei Kugeln angeschlossen sein können, # die wegen des Multithreadings praktisch gleichzeitig auf die # Methoden der Feder zugreifen können, brauchen wir ein Stoppschild, # welches die Anfrage der zweiten Kugel bei Bedarf etwas zurückhält: self.Lock = Lock() # Die Federn sollen nicht alle denselben Grauton haben. Grauton = randint(100,200) Farbcode = "#"+3*("%0X"%Grauton) # z.B. "#C8C8C8" # Canvas-Grafikobjekt der Feder; Form und Lage später durch update() self.ID = C.create_line(0,0,0,0, width=3, fill=Farbcode) # Die Federn werden hinter allen verschieblichen Objekten (Auflager und # Kugeln) angeordnet. C.tag_lower(self.ID) # Breite und Windungszahl festlegen: self.reset() # Federkraft berechnen (bzw. feststellen, dass diese null ist) # und Feder zum ersten Mal zeichnen: self.update() # Federn in die Federnliste der Start- und Zielobjekte aufnehmen Objekt1.verbinde(self,1) Objekt2.verbinde(self,2) def reset(self): """Entspannte Länge und Aussehen der Feder festlegen""" # Entspannte Länge auf aktuelle Länge setzen self.l0 = self.l # Die Feder wird als graue Zickzacklinie dargestellt: self.Federbreite = 6 # Verändert sich bei Stauchung/Streckung # Die Windungszahl ist von der Länge abhängig. Die Zacken # haben dadurch einen Winkel von etwa 60° zur Federachse. self.Wz=max(2,int(self.l0/self.Federbreite)) def update(self): """Anpassung der Feder an die neue Lage ihrer Endpunkte""" # Lage von P2 zu P1 dxy = self.x2-self.x1, self.y2-self.y1 # aktuelle geometrische Länge self.l = hypot(*dxy) # aktuelle Zugkraft F = self.k*(self.l-self.l0) # Winkel zwischen P1 und P2 W = atan2(*dxy) # x- und y-Komponente der Kraft self.Fx, self.Fy = F*sin(W), F*cos(W) # Grafik nachführen: # Die Federn bestehen aus einem kurzen geraden Stück # am Anfangs- und Endpunkt sowie einer Zickzacklinie dazwischen. # Die Anzahl der Zacken ist so gewählt, dass der Zackenabstand # im unbelasteten Zustand der Federbreite entspricht. # Startpunkt der Feder: P=[self.x1, self.y1, self.x1+(self.x2-self.x1)/self.Wz, self.y1+(self.y2-self.y1)/self.Wz] # Abstand der Zacken von der Mittellinie der Feder: # Die Modifikation der Breite um den Kehrwert der Streckung # wirkt nur innerhalb gewisser Grenzen natürlich. # Federn, die wie in dieser "Simulation" um ein Vielfaches # ihrer Länge gestreckt werden oder auf eine Länge nahe null # zusammengeschoben werden, gibt es ja in der Realität # üblicherweise eher nicht so. Wir beschränken die Streckung # daher auf das Doppelte bzw. die Hälfte des Grundwertes. Streckung = max(0.5,min(2,self.l/self.l0)) Zx = (self.y2-self.y1)/self.l*self.Federbreite/Streckung Zy = (self.x2-self.x1)/self.l*self.Federbreite/Streckung # Schleife für Zickzacklinie; immer abwechselnd ein Punkt links # und rechts von der Mittellinie … for i in range(2,self.Wz-2,2): P.append(self.x1+i*(self.x2-self.x1)/self.Wz+Zx) P.append(self.y1+i*(self.y2-self.y1)/self.Wz-Zy) P.append(self.x1+(i+1)*(self.x2-self.x1)/self.Wz-Zx) P.append(self.y1+(i+1)*(self.y2-self.y1)/self.Wz+Zy) # Zum Schluss die beiden Endpunkte: P.append(self.x2-(self.x2-self.x1)/self.Wz) P.append(self.y2-(self.y2-self.y1)/self.Wz) P.append(self.x2) P.append(self.y2) C.coords(self.ID,*P) # Soll die Federkraft angezeigt werden? if Einstellungen.Zeige_Federkräfte: # Wo ist die Mitte der Feder? fmx, fmy = (self.x1+self.x2)/2, (self.y1+self.y2)/2 # Wird die Federkraft schon angezeigt? if self.Kraftanzeige: C.itemconfig(self.Kraftanzeige,text="%.0f"%F) C.coords(self.Kraftanzeige, fmx, fmy) else: # Feder blasser zeichnen, damit der Text besser lesbar ist. Grauton = randint(200,220) C.itemconfig(self.ID, fill="#"+3*("%0X"%Grauton)) # Text der Federkraftanzeige erzeugen self.Kraftanzeige = C.create_text(fmx,fmy,text="%.0f"%F, fill="black", font=("Helvetica",12,"bold")) else: # Wird die Federkraft immer noch angezeigt? if self.Kraftanzeige: # Text löschen C.delete(self.Kraftanzeige) self.Kraftanzeige = None # Feder wieder kräftiger zeichnen Grauton = randint(100,200) C.itemconfig(self.ID, fill="#"+3*("%0X"%Grauton)) def set_P(self,i,x,y,beachteLock=True): """Ändert Koordinaten von Punkt P1 oder P2""" if beachteLock: # Stoppschild fürs Multithreading: falls zwei Kugeln # gleichzeitig diese Methode aufrufen, muss die zweite # hier warten. self.Lock.acquire() if i == 1: # Punkt 1 der Feder? self.x1, self.y1 = x, y else: self.x2, self.y2 = x, y # Kräfte durch neue Länge berechnen self.update() if beachteLock: # Für den Fall, dass eine zweite Kugel warten musste, # darf diese jetzt auch die Feder beeinflussen: self.Lock.release() def get_F(self,i): # In P1 hat die Federkraft ein anderes Vorzeichen als in P2 if i == 1: return self.Fx, self.Fy else: return -self.Fx, -self.Fy class Masse: """Ein verschieblicher Massepunkt""" def __init__(self,x,y,r=10,m=Einstellungen.Masse): self.x,self.y = x,y # Mittelpunkt self.r = r # Radius self.m = m # Masse # ID-Nummer des Kreises auf der Canvas self.ID = C.create_oval(x-r,y-r,x+r,y+r,fill = "red") self.Federn = [] # Liste verbundener Federn self.Spur = [] # Spuraufzeichnung self.vx = self.vy = 0 # Geschwindigkeitskomponenten # Um die tatsächliche Schrittzeit zu messen, holen wir uns die # aktuelle Zeit in Sekunden. self.time = perf_counter() # Multithreading: Die Berechnungen aller Massepunkte # finden parallel und unabhängig voneinander statt. self.Thread = Thread(target=self.step) self.stopped = False # Wird beim Programmende auf True gesetzt. self.Thread.start() # Werde lebendig! # Verbinde den Massepunkt mit dem ersten oder zweiten Anschlusspunkt # einer Feder def verbinde(self,Feder,Anschlusspunkt): self.Federn.append((Feder,Anschlusspunkt)) # Neuer Ort def schiebe(self,x,y): self.vx = self.vy = 0 self.x, self.y = x, y # Ende der Endlosschleife in step() def stop(self): self.stopped=True # Animation der Kugeln def step(self): # Diese Endlosschleife läuft in einem eigenen Thread while not self.stopped: # Aktuelle Vergleichszeit in Sekunden holen (als Gleitkommazahl # mit ungefähr Mikrosekundengenauigkeit, je nach Betriebssystem) t = perf_counter() # Der Zeitschritt dt für die Berechnung entspricht der # tatsächlich verstrichenen Zeit seit dem letzten Durchgang. # Zur Vermeidung von Nulldivisionen bei extrem schnellen Rechnern # wird ein Mindestwert von einer Mikrosekunde gesetzt. dt = max(0.000001,t-self.time) # Gelesene Zeit bis zum nächsten Aufruf merken self.time = t # Gleitende Ermittlung der Physikschritte pro Sekunde # über die letzten 200 Durchläufe: Einstellungen.PPS = Einstellungen.PPS*0.995 + 0.005/dt # Resultierende Kraft der angeschlossenen Federn addieren: Fx = Fy = 0 for Feder, Punkt in self.Federn: dFx, dFy = Feder.get_F(Punkt) Fx += dFx Fy += dFy # Dämpfung: 1 == keine Dämpfung, 0.01 == maximale Dämpfung D = (1-min(0.99,dt*Einstellungen.Dämpfung**2/100)) # Neue Geschwindigkeit self.vx = (self.vx+Fx/self.m*dt)*D self.vy = (self.vy+(Fy/self.m+Einstellungen.Gravitation*100)*dt)*D # Neuer Ort self.x += self.vx * dt self.y += self.vy * dt # Grafik aktualisieren C.coords(self.ID,self.x-self.r,self.y-self.r, self.x+self.r,self.y+self.r) # Federn hinterherziehen for Feder,Punkt in self.Federn: Feder.set_P(Punkt,self.x,self.y) # Soll Spurlinie gezeichnet werden? if Einstellungen.Zeige_Spur: # Gibt es schon eine Spurlinie? if self.Spur: # Wenn sich die Kugel ein Stück bewegt hat, (z. B. um # einen Kugelradius), wird die Spurlinie um # einen Punkt verlängert. if hypot(self.x-self.Spur[-2], self.y-self.Spur[-1]) > self.r: self.Spur.append(self.x) self.Spur.append(self.y) C.coords(self.Spur[0],*self.Spur[1:]) # Sonst neue Spurlinie anlegen else: # Linienfarbe soll irgendein Grünton sein r = randint(0,150) # bisschen rot … b = randint(0,150) # bisschen blau … g = randint(max(r,b),255) # … mehr grün grünlich = "#%02x%02x%02x"%(r,g,b) # Das erste Listenelement ist die Canvas-ID, danach # folgen die einzelnen Punkte der Spurlinie. Der erste # Punkt ist doppelt vorhanden, weil eine Linie aus # mindestens zwei Punkten bestehen muss. self.Spur=[C.create_line(self.x,self.y, self.x,self.y, fill=grünlich, ), self.x,self.y, self.x,self.y ] # Die Spur soll auf der Canvas ganz unten liegen, um nichts # zu verdecken. C.tag_lower(self.Spur[0]) # Spurlinie soll nicht gezeichnet werden: else: if self.Spur: # Weg damit! C.delete(self.Spur[0]) self.Spur=[] # Nicht zu schnell werden … # der maximale CPU-Anteil für diesen Thread wird mit wachsender # Kugelzahl geringer. sleep(dt*(1-1/len(Masse_Elemente))) # Markierung beim Zeichnen neuer Federn def markiere(self,einschalten): if einschalten: R = self.r*1.5 self.markID = C.create_oval(self.x-R,self.y-R, self.x+R,self.y+R, width=5,outline="green",dash=(10,)) else: C.delete(self.markID) class Lager: """Festes Auflager""" def __init__(self,x,y,r=5): self.x,self.y = x,y # Mittelpunkt self.r = r # Radius self.ID = C.create_oval(x-r,y-r,x+r,y+r,fill="blue") self.Federn = [] # Liste verbundener Federn self.F = 0 # resultierende Auflagerkraft self.Auflagerkraft_angezeigt = False self.step() # Für die Animation der Lagerkräfte # Verbinde Lager mit dem ersten oder zweiten Punkt einer Feder def verbinde(self,Feder,Punkt): self.Federn.append((Feder,Punkt)) # Neuer Ort def schiebe(self,x,y): self.x,self.y = x,y # Punkt auf der Canvas verschieben: C.coords(self.ID,self.x-self.r,self.y-self.r, self.x+self.r,self.y+self.r) # Angeschlossene Federn nachziehen: # Das Multithreading-Lock in Feder.set_P, das verhindern soll, # dass zwei Kugeln gleichzeitig auf eine Feder zugreifen, darf # hier nicht greifen, sonst blockiert das Programm. for Feder,Punkt in self.Federn: Feder.set_P(Punkt,self.x,self.y,beachteLock=False) # Markierung beim Zeichnen neuer Federn def markiere(self,einschalten): if einschalten: R = self.r*1.5 self.markID = C.create_oval(self.x-R,self.y-R, self.x+R,self.y+R, width=5,outline="green",dash=(10,)) else: C.delete(self.markID) # Anzeige der Lagerkräfte: def step(self): # Soll die Auflagerkraft überhaupt angezeigt werden? if Einstellungen.Zeige_Auflagerkräfte: # Summe der auf das Lager wirkenden Kräfte bilden Fx = Fy = 0 for Feder,Punkt in self.Federn: dFx, dFy = Feder.get_F(Punkt) Fx += dFx Fy += dFy # Resultierende Kraft ausrechnen self.F = hypot(Fx,Fy) # Skalierung für eine freundliche Darstellungsgröße; # Die größte Auflagerkraft erhält die Länge 100. max_abs_F = 1 for E in Lager_Elemente: max_abs_F = max(max_abs_F, E.F) F_Skalierung = 100 / max_abs_F # Position des Textes x_F = self.x + Fx * F_Skalierung y_F = self.y + Fy * F_Skalierung # Endpunkte des Kraftpfeils x1_P = self.x + self.r * Fx/self.F if self.F !=0 else self.x y1_P = self.y + self.r * Fy/self.F if self.F !=0 else self.y x2_P = self.x + 0.9 * (x_F-self.x) y2_P = self.y + 0.9 * (y_F-self.y) # Wird die Auflagerkraft schon angezeigt? if self.Auflagerkraft_angezeigt: C.itemconfig(self.AT_ID,text="%.0f"%self.F) C.coords(self.AT_ID,x_F,y_F) C.coords(self.AP_ID,x1_P,y1_P,x2_P,y2_P) else: self.AP_ID = C.create_line(x1_P,y1_P,x2_P,y2_P, arrow="first",arrowshape=(10,12,6), width=3,fill="blue") self.AT_ID = C.create_text(x_F,y_F, text="%.0f"%self.F,fill="black", font=("Helvetica",12,"bold")) self.Auflagerkraft_angezeigt = True else: # Wird sie immer noch angezeigt? if self.Auflagerkraft_angezeigt: C.delete(self.AT_ID) C.delete(self.AP_ID) self.Auflagerkraft_angezeigt = False # Bis gleich … T.after(40,self.step) # 40 ms; 1/25 s # Nicht zu oft; 25 mal pro Sekunde ist mehr als genug. # Wer mag, kann die beiden Klassen „Lager“ und „Masse“ auch von einer # neu anzulegenden Klasse „Verschieblicher_Punkt“ ableiten, damit oben # keine Redundanzen auftreten. # Geometrie und Zusammenhänge einer Beispielzusammenstellung: # Variante 1: „Hampelmann“ ##L1 = Lager(300,100) ##L2 = Lager(500,100) ##L3 = Lager(200,600) ##L4 = Lager(600,600) ## ##M1 = Masse(400,200) ##M2 = Masse(400,300) ## ##F1 = Feder(L1,M1) ##F2 = Feder(L2,M1) ##F3 = Feder(M2,M1) ##F4 = Feder(M2,L3) ##F5 = Feder(M2,L4) ## ##Masse_Elemente = [M1,M2] ##Lager_Elemente = [L1,L2,L3,L4] ##Verschiebliche_Elemente = [M1,M2,L1,L2,L3,L4] ##Abhängige_Elemente = [F1,F2,F3,F4,F5] # Variante 2: „Minimalistisch“ L1 = Lager(400,100) M1 = Masse(400,200) F1 = Feder(L1,M1) Masse_Elemente = [M1] Lager_Elemente = [L1] Verschiebliche_Elemente = [M1,L1] Abhängige_Elemente = [F1] # # Interaktionsteil # # Die vorhandenen Massen und Lager können durch Anklicken und Ziehen # verschoben werden. # Welches ist das zur Position x,y nächstliegende Objekt? def NächstesElement(x,y): a_min = 1E10 for i,E in enumerate(Verschiebliche_Elemente): a = hypot(x-E.x,y-E.y) if a<a_min: a_min=a i_min=i return Verschiebliche_Elemente[i_min] # Verschiebe das dem Mauszeiger am nächsten liegende Objekt class dragdrop: """Merkt sich das gerade zu verschiebende Objekt""" E = None def drag(event): """Klicken und Ziehen""" if not dragdrop.E: dragdrop.E = NächstesElement(event.x,event.y) C.config(cursor="fleur") # Wird als zugreifende Hand dargestellt dragdrop.E.schiebe(event.x,event.y) def drop(event): """Loslassen""" if dragdrop.E: dragdrop.E = None C.config(cursor="") # Erzeuge einen neuen Massepunkt und verbinde ihn (nur bei vorhandener # Gravitation) mit dem nächsten Element, damit er nicht herunterfällt. def neueMasse(event): M = Masse(event.x,event.y,m=Einstellungen.Masse) if Einstellungen.Gravitation: E = NächstesElement(event.x,event.y) F = Feder(E,M,Einstellungen.Federsteifigkeit) Abhängige_Elemente.append(F) Masse_Elemente.append(M) Verschiebliche_Elemente.append(M) # Erzeuge neues Lager def neuesLager(event): L = Lager(event.x,event.y) Lager_Elemente.append(L) Verschiebliche_Elemente.append(L) # Erzeuge eine neue Feder in zwei Schritten durch die Auswahl eines # Start- und eines Zielelements. Jeweils das dem Mauszeiger am nächsten # liegende Objekt wird ausgewählt. class Federstatus: """Zur Speicherung des Startelements einer neuen Feder""" Startelement = None def neueFeder(event): """Erzeugt neue Feder vom Start- zum Zielobjekt""" E = NächstesElement(event.x,event.y) # Gibt es noch kein Startelement? -> neue Feder if not Federstatus.Startelement: # Wir markieren das Element, von dem die Feder ausgehen soll. E.markiere(True) # Außerdem merken wir uns dieses Element für später … Federstatus.Startelement = E else: # … also für jetzt ;-) # Das ist doch jetzt wirklich ein anderes Element als vorhin, oder? if E != Federstatus.Startelement: # Dann her mit der Feder! F = Feder(Federstatus.Startelement,E,Einstellungen.Federsteifigkeit) Abhängige_Elemente.append(F) # Die Markierung kann wieder weg Federstatus.Startelement.markiere(False) Federstatus.Startelement = None def Rechtsklick(event): """Entspricht dem Drücken der Tasten „M“, „L“ oder „F“""" if RR_Variable.get()=="M": neueMasse(event) elif RR_Variable.get()=="L": neuesLager(event) elif RR_Variable.get()=="F": neueFeder(event) # Geordnetes Beenden der ganzen parallel laufenden Threads. # Wenn wir uns nicht darum kümmern, gibt es unästhetische Fehlermeldungen # wie z.B. _tkinter.TclError: invalid command name “.4302957584” def Canvasschließen(event=None): # Im Moment laufen alle Kugeln in ihren eigenen # Endlosschleifen. Wir teilen ihnen mit, dass sie diese # doch jetzt bitte bald mal beenden sollen. for E in Masse_Elemente: E.stop() # Wir geben ihnen noch reichlich Zeit … sleep(0.1) TE.destroy() # Schon mal das Einstellungsfenster schließen … # … noch ein ganz kleines Päuschen … sleep(0.1) # … letzte Aufräumarbeiten auf der Canvas … T.update() # … und weg! T.destroy() # Jetzt auch das Canvasfenster schließen. ########## Event-Bindungen im Canvas-Fenster ############# C.bind("<B1-Motion>",drag) C.bind("<ButtonRelease-1>",drop) C.bind("<Button-3>",Rechtsklick) T.bind("m",neueMasse) T.bind("l",neuesLager) T.bind("f",neueFeder) T.bind("<F1>",Hilfe) T.protocol("WM_DELETE_WINDOW", Canvasschließen) ########### Einstellungen-Fenster ########### TE = Toplevel() TE.title("Einstellungen") TE.bind("<F1>",Hilfe) TE.protocol("WM_DELETE_WINDOW", Canvasschließen) # Die Gridzeile 0 mit dem Reglerpult soll sich in # der Größe anpassen, wenn das Fenster vergrößert wird. TE.rowconfigure(0,weight=1) # Die 2 Spalten des Hauptfensters sollen sich gleichmäßig verteilen können. TE.columnconfigure(0,weight=1) TE.columnconfigure(1,weight=1) # Das Reglerpult SLF = LabelFrame(TE,text="Reglerpult:",padx=5,pady=5) SLF.grid(row=0,column=0,columnspan=2,sticky="wens",padx=5,pady=5) # Zeile 1 im Labelframe (die Regler) soll dessen Größenanpassung übernehmen, # außerdem soll sie mindestens 200 Pixel hoch sein, damit die # Skalenzahlen gut zu erkennen sind. SLF.rowconfigure(1,weight=1,minsize=200) # Die 4 Spalten sollen sich gleichmäßig verteilen können. SLF.columnconfigure(0,weight=1) SLF.columnconfigure(1,weight=1) SLF.columnconfigure(2,weight=1) SLF.columnconfigure(3,weight=1) # Schieberegler Gravitation Label(SLF,text="Gravitation").grid(row=0,column=0) def SG_command(event): Einstellungen.Gravitation = SG.get() SG = Scale(SLF, from_=-2, to=20, width=20, orient="vertical", tickinterval=2, resolution=0.01, command=SG_command) SG.set(Einstellungen.Gravitation) # Anfangswert des Reglers: 9.81 SG.grid(row=1,column=0,sticky="nsew") # Schieberegler Federsteifigkeit Label(SLF,text="Feder-nsteifigkeit").grid(row=0,column=1) def SF_command(event): Einstellungen.Federsteifigkeit = SF.get() # Allen Federn die eingestellte Steifigkeit zuweisen: for Feder in Abhängige_Elemente: Feder.k = Einstellungen.Federsteifigkeit SF = Scale(SLF, from_=0, to=10000, width=20, orient="vertical", tickinterval=1000, command=SF_command) SF.set(Einstellungen.Federsteifigkeit) # Anfangswert des Reglers: 100 SF.grid(row=1,column=1,sticky="nsew") # Schieberegler Kugelmasse Label(SLF,text="Kugelmasse").grid(row=0,column=2) def SM_command(event): Einstellungen.Masse = SM.get() # Der Regler beginnt zwar aus gestalterischen Gründen bei null, # dennoch ist eine Masse von null unerwünscht (Nulldivision bei # Beschleunigung auf Warp 10). # Die Mindestmasse wird daher immer auf 1 gesetzt. if Einstellungen.Masse == 0: Einstellungen.Masse=1 SM.set(Einstellungen.Masse) # Alle Massepunkte des Systems abarbeiten: for E in Masse_Elemente: E.m = Einstellungen.Masse SM = Scale(SLF, from_=0, to=100, width=20, orient="vertical", tickinterval=10, command=SM_command) SM.set(Einstellungen.Masse) # Anfangswert 5 SM.grid(row=1,column=2,sticky="nsew") # Schieberegler Dämpfung Label(SLF,text="Dämpfung").grid(row=0,column=3) def SD_command(event=None): Einstellungen.Dämpfung = SD.get() SD = Scale(SLF, from_=0, to=100, width=20, orient="vertical", tickinterval=10, command=SD_command) SD.set(Einstellungen.Dämpfung) SD.grid(row=1,column=3,sticky="nsew") # Checkbuttons zur Anzeigesteuerung CLF = LabelFrame(TE,text="Anzeigen:",padx=5,pady=5) CLF.grid(row=1,column=0,sticky="wen",padx=5,pady=5) # Checkbutton Federkräfte def CF_command(): Einstellungen.Zeige_Federkräfte = CF_Variable.get()=="ja" CF_Variable = StringVar() CF_Variable.set("nein") CF = Checkbutton(CLF, command=CF_command, variable=CF_Variable, onvalue="ja", offvalue="nein", text = "Federkräfte") CF.grid(row=0, column=0, sticky="w") # Checkbutton Auflagerkräfte def CA_command(): Einstellungen.Zeige_Auflagerkräfte = CA_Variable.get()=="ja" CA_Variable = StringVar() CA_Variable.set("nein") CA = Checkbutton(CLF, command=CA_command, variable=CA_Variable, onvalue="ja", offvalue="nein", text = "Auflagerkräfte") CA.grid(row=1, column=0, sticky="w") # Checkbutton Spuraufzeichnung def CS_command(): Einstellungen.Zeige_Spur = CS_Variable.get()=="ja" CS_Variable = StringVar() CS_Variable.set("nein") CS = Checkbutton(CLF,command=CS_command, variable=CS_Variable, onvalue="ja", offvalue="nein", text = "Spurlinien") CS.grid(row=2, column=0, sticky="w") # Radiobuttons für Auswahl der Rechtsklick-Aktion RLF = LabelFrame(TE,text="Rechtsklick erzeugt:",padx=5,pady=5) RLF.grid(row=1,column=1,sticky="wen",padx=5,pady=5) RR_Variable = StringVar() RR_Variable.set("M") Radiobutton(RLF,variable=RR_Variable,text="Massepunkt",value="M",underline=0 ).grid(row=0,column=0,sticky="w") Radiobutton(RLF,variable=RR_Variable,text="Auflager",value="L",underline=3 ).grid(row=1,column=0,sticky="w") Radiobutton(RLF,variable=RR_Variable,text="Feder",value="F",underline=0 ).grid(row=2,column=0,sticky="w") # Buttons für Schnelleinstellungen # Button "Antigravity" (https://xkcd.com/353/) def BG_command(): """Schaltet Schwerelosigkeit ein""" SG.set(0) Button(TE,text="Antigravity",command=BG_command ).grid(row=2,column=0,sticky="we") # Button "Entspannen" def BE_command(): """Setzt Federkräfte auf null. Die aktuelle Federlänge l wird zur entspannten Länge l0.""" for Feder in Abhängige_Elemente: Feder.reset() Button(TE,text="Entspannen",command=BE_command ).grid(row=2,column=1,sticky="we") # Infozeile wird sekündlich aktualisiert def Infozeilenupdate(event=None): A = len(Lager_Elemente) M = len(Masse_Elemente) F = len(Abhängige_Elemente) Infozeile.config(text= "%i Auflager, %i Massepunkt%s, %i Feder%s, %i Physikschritte/s"%( A,M,"" if M==1 else "e",F,"" if F==1 else "n",Einstellungen.PPS)) TE.after(1000,Infozeilenupdate) Infozeile = Label(TE) Infozeile.grid(row=3,column=0,columnspan=2,sticky="w") Infozeilenupdate() # Go! T.mainloop()
--
Dipl.-Ing. Martin Vogel
Leiter des Bauforums
Bücher:
CAD mit BricsCAD
Bauinformatik mit Python
gesamter Thread:
- Simulation chaotischer Masse-Feder-Systeme mit Python 3 -
Martin Vogel,
02.02.2016, 13:23
- Genauere Zeitnahme in Windows - Martin Vogel, 22.12.2016, 12:27