Bauforum-Logo

Forum Bauen und Umwelt

log in | registrieren

zurück zum Forum
  Mix-Ansicht

Genauere Zeitnahme in Windows (Software)

verfasst von Martin Vogel Homepage E-Mail, Dortmund / Bochum, 22.12.2016, 12:27 Uhr

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.


[image]

Download:
[image]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/forum_entry.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.\
\n\n\
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“.\
\n\n\
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.\
\n\n\
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.\
\n\n\
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. \
\n\n\
Viel Spaß damit!\
\n\n\
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

Heute schon programmiert? Einführung in Python 3 (PDF)

antworten
 



gesamter Thread:

zurück zum Forum
  Mix-Ansicht
Forum Bauen und Umwelt | Kontakt | Impressum
8440 Postings in 4045 Threads, 1094 registrierte User, 38 User online (0 reg., 38 Gäste)
powered by my little forum  RSS-Feed  ^
map | new