QResizeEvent

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

Hallo,

wieso geht der resize event selbstständig in eine endlos rekursion?

Code: Alles auswählen

#!/usr/bin/env python

import sys

from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QToolButton
from PySide6.QtCore import QSize, Qt
from PySide6.QtGui import QPainter, QPen, QPixmap, QGradient, QResizeEvent, QFont

class MainForm(QWidget):
    def __init__(self):
        super().__init__()

        self.layout = QVBoxLayout()
        self.button = QToolButton()
        self.layout.addWidget(self.button)
        self.setLayout(self.layout)

    def resizeEvent(self, event: QResizeEvent) -> None:
        g = self.button.geometry()
        actual_size = g.size()
        pixmap = QPixmap(actual_size)
        pixmap.fill(Qt.GlobalColor.lightGray)
        gradient = QGradient(QGradient.SoftCherish)

        painter = QPainter(pixmap)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(Qt.GlobalColor.black, 5, Qt.SolidLine))
        painter.setBrush(gradient)
        painter.drawEllipse(g.width() / 2 - 150, g.height() / 2 - 150, 300, 300)
        painter.end()
        
        self.button.setIcon(pixmap)
        self.button.setIconSize(actual_size)

        return super().resizeEvent(event)

def main():
    app = QApplication(sys.argv)
    window = MainForm()
    window.show()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()
Danke und Gruß
Benutzeravatar
__blackjack__
User
Beiträge: 13187
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Du fragst vom Button die Grösse ab und setzt dann ein Icon in dieser Grösse *in* den Button. Wodurch der grösser wird, wodurch das Fenster grösser wird, und ein resize-Ereignis auslöst. Und so weiter.

In der `__init__()` wird übrigens die Methode `QWidget.layout()` durch ein Layout ersetzt. Das sollte wohl eher nicht so sein.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

Du meinst so?:

Code: Alles auswählen

        self.qvbox_layout = QVBoxLayout()
        self.button = QToolButton()
        self.qvbox_layout.addWidget(self.button)
        self.setLayout(self.qvbox_layout)
Die Größe auf das QWidget abgefragt hat den selben Effekt:

Code: Alles auswählen

       g = self.geometry()
Stehe da etwas auf der Leitung. Kannst Du beschreiben, wie das zu korrigieren ist? Bzw. laut Doku braucht es ein QPushButton, QToolButton oder QLabel, um eine pixmap darzustellen. Geht das nicht auch anders? Grundsätzlich möchte ich in einem QVBoxLayout (oder in einem beliebigen Layout) neben ein paar Buttons und Eingabefeldern nur einen 2D-Grafkibereich haben (der aber das resize event unterstützt). Kann ich da nicht ein QWidget einbetten, in dem die pixmap steckt?
Benutzeravatar
__blackjack__
User
Beiträge: 13187
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Warum bindest Du das Layout überhaupt an das Objekt? In der Methode selbst reicht es als lokale Variable, und wenn Du das nach dem setzen irgendwo anders brauchst, dann ist das ja über die `layout()`-Methode erreichbar.

Heisst das Du brauchst die Funktionalität einer Schaltfläche gar nicht? Dann implementiere doch einfach `paintEvent()` auf einer von `QWidget` abgeleiteten Klasse und mal da direkt drauf rum. Oder eben direkt auf der Schaltfläche, denn die ist ja auch von `QWidget` abgeleitet.

Edit: Was ist denn das eigentliche Ziel?
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

__blackjack__ hat geschrieben: Donnerstag 9. Mai 2024, 20:29 @mechanicalStore: Warum bindest Du das Layout überhaupt an das Objekt? In der Methode selbst reicht es als lokale Variable, und wenn Du das nach dem setzen irgendwo anders brauchst, dann ist das ja über die `layout()`-Methode erreichbar.
In sämtlchen Beispielen der Doku ist das so vorgeschlagen. Und als lokale Variable habe ich es doch deklariert?! Oder was meinst Du damit?
Heisst das Du brauchst die Funktionalität einer Schaltfläche gar nicht? Dann implementiere doch einfach `paintEvent()` auf einer von `QWidget` abgeleiteten Klasse und mal da direkt drauf rum. Oder eben direkt auf der Schaltfläche, denn die ist ja auch von `QWidget` abgeleitet.
Ein Versuch dazu (das resize event ruft ja das paint event auf, also müsste das als Test erst mal genügen)...

Code: Alles auswählen

import sys

from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QToolButton
from PySide6.QtCore import QSize, Qt
from PySide6.QtGui import QPainter, QPen, QPixmap, QGradient, QResizeEvent, QFont

class DrawingArea(QWidget):
    def __init__(self):
        super().__init__()

class MainForm(QWidget):
    def __init__(self):
        super().__init__()

        self.qvbox_layout = QVBoxLayout()
        self.button = QPushButton('Start')
        self.drawing_area = DrawingArea()
        self.qvbox_layout.addWidget(self.button)
        self.qvbox_layout.addWidget(self.drawing_area)
        self.setLayout(self.qvbox_layout)

    def resizeEvent(self, event: QResizeEvent) -> None:
        g = self.drawing_area.geometry()
        actual_size = g.size()
        gradient = QGradient(QGradient.SoftCherish)
        painter = QPainter(self.drawing_area)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(Qt.GlobalColor.black, 5, Qt.SolidLine))
        painter.setBrush(gradient)
        painter.drawEllipse(g.width() / 2 - 150, g.height() / 2 - 150, 300, 300)
        painter.end()
 
        return super().resizeEvent(event)

def main():
    app = QApplication(sys.argv)
    window = MainForm()
    window.show()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()
...führt hierzu (außerdem wird nur der Button angezeigt):

Code: Alles auswählen

QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setRenderHint: Painter must be active to set rendering hints
QPainter::setPen: Painter not active
QPainter::setBrush: Painter not active
QPainter::drawEllipse: Painter not active
QPainter::end: Painter not active, aborted
Edit: Was ist denn das eigentliche Ziel?
Ein paar Eingabefelder, ein Start-Button. Es kommen ein paar Werte rein und anhand dessen wird eine Punktemenge berechnet, die dann grafisch angezeigt (zweidimensional) werden soll.
Benutzeravatar
__blackjack__
User
Beiträge: 13187
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Deklariert ist da nichts und wenn da `self.` davor steht ist das nicht lokal sondern ein Attribut auf der Klasse. Und das muss und sollte es halt nicht sein, sondern einfach nur ein lokaler Name innerhalb der Methode:

Code: Alles auswählen

        layout = QVBoxLayout()
        self.button = QToolButton()
        layout.addWidget(self.button)
        self.setLayout(layout)
Das ist das falsche Ereignis auf dem falschen Objekt. `paintEvent()`, nicht `resizeEvent()`, und auf dem Objekt auf dem auch tatsächlich gemalt werden soll:

Code: Alles auswählen

import sys

from PySide6.QtCore import QSize, Qt
from PySide6.QtGui import QGradient, QPainter, QPen
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget


class DrawingArea(QWidget):
    def __init__(self):
        super().__init__()
        self.setMinimumSize(QSize(306, 306))

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(Qt.GlobalColor.black, 5, Qt.SolidLine))
        painter.setBrush(QGradient(QGradient.SoftCherish))
        painter.drawEllipse(
            self.width() // 2 - 150, self.height() // 2 - 150, 300, 300
        )
        painter.end()

        return super().paintEvent(event)


class Window(QWidget):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout(self)
        layout.addWidget(QPushButton("Start"))
        layout.addWidget(DrawingArea())


def main():
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
Wobei eigentlich nicht so wirklich klar ist warum das überhaupt eines der beiden Ereignisse sein muss, denn der Code macht ja nichts als diesen Kreis zu zentrieren. Was man auch mit einem statischen Bild das man *einmal* zeichnet in einem Label und Layouts erreichen könnte.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

@__blackjack__: vielen Dank für die hilfreichen Tipps und Korrekturen.
__blackjack__ hat geschrieben: Donnerstag 9. Mai 2024, 22:34 Wobei eigentlich nicht so wirklich klar ist warum das überhaupt eines der beiden Ereignisse sein muss, denn der Code macht ja nichts als diesen Kreis zu zentrieren. Was man auch mit einem statischen Bild das man *einmal* zeichnet in einem Label und Layouts erreichen könnte.
Wie schon erwähnt, geht es um eine Punktemenge, die angezeigt werden soll und die sich beim Ändern der Eingabewerte ständig ändert. Das mit dem Kreis ist ja nur ein kleiner Test als Minimalbeispiel. Denn wenn ich 1 Kreis malen kann, dann auch 1000 Punkte, die berechnet werden.

Es gibt aber ein weiteres Problem beim Zeichnen einer Punktemenge (hier nur wieder eine Test-Liste mit 3 Punkten). Die X und Y Werte von QPoint lassen sich nachträgliich offenbar nicht mehr ändern (da ich sie zentrieren und später auch skalieren will);

Code: Alles auswählen

import sys

from PySide6.QtCore import QSize, Qt, QPoint
from PySide6.QtGui import QGradient, QPainter, QPen
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget


class DrawingArea(QWidget):
    def __init__(self, evol_points):
        super().__init__()
        self.setMinimumSize(QSize(306, 306))
        self.evol_points = evol_points

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setPen(QPen(Qt.GlobalColor.black, 5, Qt.SolidLine))
        # painter.setBrush(QGradient(QGradient.SoftCherish))
        painter.drawEllipse(
            self.width() // 2 - 150, self.height() // 2 - 150, 300, 300
        )
        painter.setPen(QPen(Qt.GlobalColor.red, 10, Qt.SolidLine))
        painter.drawPoint(QPoint(self.width() // 2, self.height() // 2))

        painter.setPen(QPen(Qt.GlobalColor.darkBlue, 10, Qt.SolidLine))
        for evol in self.evol_points:
            evol.setX(evol.x + self.width() // 2)     # <----------- Fehler
            evol.setY(evol.y + self.height() // 2)    # <----------- Fehler
            painter.drawPoint(evol)

        painter.end()

        return super().paintEvent(event)

class Window(QWidget):
    def __init__(self, evol_points):
        super().__init__()
        layout = QVBoxLayout(self)
        layout.addWidget(QPushButton("Start"))
        layout.addWidget(DrawingArea(evol_points))


def main():
    evol_points = [QPoint(10, 10), QPoint(20, 30), QPoint(40, 50)]
    app = QApplication(sys.argv)
    window = Window(evol_points)
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
Benutzeravatar
snafu
User
Beiträge: 6747
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@mechanicalStore
Bitte schreibe nicht bloß "Fehler" dran, sondern kopiere immer auch die komplette Fehlermeldung.

In dem Fall fehlen die Klammern bei den Abfragen von x und y der einzelnen Punkte. Das sind in Qt nämlich Methoden.

Somit müsste es so lauten:

Code: Alles auswählen

for evol in self.evol_points:
    evol.setX(evol.x() + self.width() // 2)
    evol.setY(evol.y() + self.height() // 2)
    painter.drawPoint(evol)
Benutzeravatar
__blackjack__
User
Beiträge: 13187
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei das zwar die Fehlermeldung verschwinden lässt, aber semantisch falsch ist, weil die Punkte ja nun bei jedem neu zeichnen verändert werden und so ganz schnell aus dem sichtbaren Bereich heraus geschoben werden. Das alleinige *anzeigen* darf die Koordinaten nicht verändern.

Ungetestet:

Code: Alles auswählen

import sys

from PySide6.QtCore import QSize, Qt, QPoint
from PySide6.QtGui import QPainter, QPen
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget


class DrawingArea(QWidget):
    def __init__(self, evol_points):
        super().__init__()
        self.setMinimumSize(QSize(306, 306))
        self.evol_points = evol_points

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        
        center = self.rect().center()
        painter.setPen(QPen(Qt.GlobalColor.black, 5))
        painter.drawEllipse(center, 150, 150)

        painter.setPen(QPen(Qt.GlobalColor.red, 10))
        painter.drawPoint(center)

        painter.setPen(QPen(Qt.GlobalColor.darkBlue, 10))
        for point in self.evol_points:
            painter.drawPoint(point + center)

        painter.end()
        return super().paintEvent(event)


class Window(QWidget):
    def __init__(self, evol_points):
        super().__init__()
        layout = QVBoxLayout(self)
        layout.addWidget(QPushButton("Start"))
        layout.addWidget(DrawingArea(evol_points))


def main():
    app = QApplication(sys.argv)
    evol_points = [QPoint(10, 10), QPoint(20, 30), QPoint(40, 50)]
    window = Window(evol_points)
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
Wobei man sich vielleicht auch überlegen kann/sollte ob es nicht vielleicht Sinn macht das Koordinatensystem so zu ändern, dass der Nullpunkt in der Mitte liegt, statt selbst bei jeder Zeichenoperation diese Verschiebung zu machen (ungetestet):

Code: Alles auswählen

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.translate(self.rect().center())
        
        painter.setPen(QPen(Qt.GlobalColor.black, 5))
        painter.drawEllipse(QPoint(0, 0), 150, 150)

        painter.setPen(QPen(Qt.GlobalColor.red, 10))
        painter.drawPoint(0, 0)

        painter.setPen(QPen(Qt.GlobalColor.darkBlue, 10))
        painter.drawPoints(self.evol_points)

        painter.end()
        return super().paintEvent(event)
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

@snafu: Danke für den Hinweis. Steht ja auch so in der Doku. Es war wohl doch zu spät nach Mitternacht...
__blackjack__ hat geschrieben: Freitag 10. Mai 2024, 06:56 Wobei das zwar die Fehlermeldung verschwinden lässt, aber semantisch falsch ist, weil die Punkte ja nun bei jedem neu zeichnen verändert werden und so ganz schnell aus dem sichtbaren Bereich heraus geschoben werden. Das alleinige *anzeigen* darf die Koordinaten nicht verändern.
Das ist mir dann auch sehr schnell aufgefallen. :-)
Ungetestet:
...

Wobei man sich vielleicht auch überlegen kann/sollte ob es nicht vielleicht Sinn macht das Koordinatensystem so zu ändern, dass der Nullpunkt in der Mitte liegt, statt selbst bei jeder Zeichenoperation diese Verschiebung zu machen (ungetestet):

Code: Alles auswählen

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
	...
	
Oh, das ist ja sogar noch besser.

Jetzt kommt dann hinzu, dass die Punkte in einem anderen Modul erzeugt werden. Die werden ja im Moment nur beim Initialisieren mitgegeben, was aber dann nicht geht, da sie dann noch längst nicht berechnet sind. Kann man die Punkteliste mit einem Signal komplett zur DrawingArea schicken, sodas ich ohne Neustart des Programms immer wieder neue Werte berechnen und anzeigen kann? Also eine Art model/view nachbilden?
Benutzeravatar
__blackjack__
User
Beiträge: 13187
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ja klar kann man das Attribut ändern das die Punkte enthält. Und dann ein `update()`-Aufruf, damit in naher Zukunft `repaintEvent` ausgeführt wird.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

Hier mal im Ganzen. Irgendwie bekomme ich die richtige Skalierung nicht hin. Beim größer ziehen in Y laufen die Punkte aus dem Sichtfeld raus. Probe z.B. mit modul=1 und zähnezahl=23. Die Punkte eval_int_points sind mit 100 multipliziert, da die eval_float_point so klein sind, dass beim umwandeln nach int (was QPoint ja von Haus aus schon macht) alle Punkte auf einem Haufen liegen würden. Daher muss besonders in Y so "stark" verschoben werden, d.h. die fehlerhafte Skalierung wirkt sich da dann am meisten aus.

main.py

Code: Alles auswählen

#!/usr/bin/env python

import sys

from PySide6.QtCore import QSize, Qt, QPoint
from PySide6.QtGui import QGradient, QPainter, QPen
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from view import Window


def main():
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
GearWheel.py

Code: Alles auswählen

#!/usr/bin/env python

from math import pi,sin, cos, tan, acos
from PySide6.QtCore import QPoint

ALPHA_0 = 20.0 * pi / 180.0     # Normaleingriffswinkel rad (= 20° deg)

class GearWheel():
    def __init__(self, gear_modul, teeth_count):
        self.gear_modul = gear_modul
        self.teeth_count = teeth_count

        self.evol_float_points = []
        self.evol_int_points = []
        self.bounding_box = (1, 2, 3, 4, 5, 6)

    def calculate_basics(self):
        # self.gear_modul                                                                                           # Modul
        # self.teeth_count                                                                                          # Zähnezahl
        self.partial_diameter = self.teeth_count * self.gear_modul                                                  # Teilkreis Durchmesser
        self.circumferential_division = self.gear_modul * pi                                                        # Umfangsteilung
        self.circumferential_angle_rad = self.circumferential_division / (self.partial_diameter / 2.0)              # Umfangswinkel Radian
        self.circumferential_angle_deg = self.circumferential_angle_rad / pi * 180.0                                # Umfangswinkel Degree
        self.tooth_head_gap = self.gear_modul * 0.167                                                               # Zahnkopfspiel
        self.foot_circle_diameter = self.partial_diameter - 2.0 * (self.gear_modul + self.tooth_head_gap)           # Fusskreis Durchmesser
        self.head_circle_diameter = self.partial_diameter + 2.0 * self.gear_modul                                   # Kopfkreis Durchmesser
        self.head_circle_diameter_with_gap = self.partial_diameter + 2.0 * (self.gear_modul + self.tooth_head_gap)  # Kopfkreis Durchmesser + Spiel
        self.base_circle_diameter = self.partial_diameter * cos(ALPHA_0)                                            # Grundkreis Durchmesser
        self.tooth_head_height = self.gear_modul                                                                    # Zahnkopf Höhe
        self.tooth_foot_height = self.gear_modul + self.tooth_head_gap                                              # Zahnfuss Höhe
        self.tooth_height_over_all = 2.0 * self.gear_modul + self.tooth_head_gap                                    # Zahn Höhe insgesamt
 
        print(f'Modul={self.gear_modul}\nAnzahl Zähne={self.teeth_count}\nTeilkreis_Durchmesser={self.partial_diameter}')
        print(f'Umfangsteilung={self.circumferential_division}\nUmfangswinkel_Radian={self.circumferential_angle_rad}')
        print(f'Zahnkopfspiel={self.tooth_head_gap}\nUmfangswinkel_Degree={self.circumferential_angle_deg}')
        print(f'Fusskreis_durchmesser={self.foot_circle_diameter}\nKopfkreis_Durchmesser={self.head_circle_diameter}')
        print(f'Kopfkreis_Durchmesser + Spiel={self.head_circle_diameter_with_gap}')
        print(f'Grundkreis_Durchmesser={self.base_circle_diameter}\nZahnkopf_Höhe={self.tooth_head_height}')
        print(f'Zahnfuss_Höhe={self.tooth_foot_height}\nZahn_Höhe={self.tooth_height_over_all}')
        print(f'Minimaler Section Wert={self.foot_circle_diameter}\nMaximaler Section Wert={self.head_circle_diameter}')

    def calculate_evolvente_thickness(self, section):
        if section < self.base_circle_diameter or section > self.head_circle_diameter: return

        s_0 = self.gear_modul * pi / 2.0
        d_0 = self.partial_diameter
        gamma_0 = tan(ALPHA_0) - ALPHA_0
        alpha = acos(d_0 / section * cos(ALPHA_0))
        gamma = tan(alpha) - alpha
        s = section * (s_0 / d_0 + gamma_0 - gamma)
        # print(f'cos(alpha)={d_0 / section * cos(ALPHA_0):2.15f}   Section={section:2.3f}   S={s:2.3f}')
        return s
    
    def calculate_complete_tooth(self):
        self.evol_float_points.clear()
        self.evol_int_points.clear()
        self.bounding_box = ()

        bounding_box_min_x = 10000
        bounding_box_max_x = -10000
        bounding_box_min_y = 10000
        bounding_box_max_y = -10000

        section = self.base_circle_diameter
        while(section <= self.head_circle_diameter):

            evol_point = self.calculate_evolvente_thickness(section)
            self.evol_float_points.append((evol_point / 2.0, section / 2.0))
            self.evol_float_points.append((evol_point / 2.0 * -1, section / 2.0))
            self.evol_int_points.append(QPoint(evol_point * 100.0, section * 100.0))
            self.evol_int_points.append(QPoint(evol_point * 100.0 * -1, section * 100.0))

            bounding_box_min_x = min(bounding_box_min_x, int(evol_point * -100.0))
            bounding_box_max_x = max(bounding_box_max_x, int(evol_point * 100.0))
            bounding_box_min_y = min(bounding_box_min_y, int(section * 100.0))
            bounding_box_max_y = max(bounding_box_max_y, int(section * 100.0))
            section += 0.1
        
        self.bounding_box = (bounding_box_min_x, bounding_box_max_x, bounding_box_min_y, bounding_box_max_y, bounding_box_max_x - bounding_box_min_x, bounding_box_max_y - bounding_box_min_y)

def main():
    gw = GearWheel(1.0, 23)
    gw.calculate_basics()
    gw.calculate_complete_tooth()
    x = gw.evol_int_points
    print(x)
 

if __name__ == '__main__':
    main()

view.py

Code: Alles auswählen

#!/usr/bin/env python

import sys

from PySide6.QtCore import QSize, Qt, QPoint, Slot, Signal
from PySide6.QtGui import QGradient, QPainter, QPen, QBrush
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget, QLabel, QLineEdit

from GearWheel import GearWheel

class DrawingArea(QWidget):
    def __init__(self):
        super().__init__()
        self.setMinimumSize(QSize(400, 350))
        self._scale = 1.0
        self._evol_int_points = []
        self._bounding_box = (1, 2, 3, 4, 5, 6)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        g = self.geometry()
        bbx_min_x , bb_max_x, bb_min_y, bb_max_y, bb_width, bb_height = self._bounding_box
        
        scale_quotient = g.height() / bb_height

        center_x = ((g.width() - bb_width) / 2 - bbx_min_x) # * scale_quotient
        center_y = ((g.height() - bb_height) / 2  + bb_max_y) * scale_quotient
        
        painter.translate(center_x, center_y)
        painter.scale(scale_quotient, scale_quotient)

        painter.setPen(QPen(Qt.GlobalColor.red, 5 / scale_quotient))
        for evol_point in self._evol_int_points:
            painter.drawPoint(QPoint(evol_point.x(), evol_point.y() * -1))
        # painter.drawPoints(self._evol_int_points)

        painter.end()
        return super().paintEvent(event)

class Window(QWidget):
    def __init__(self):
        super().__init__()

        layout = QVBoxLayout(self)

        button = QPushButton("Start")
        label_m = QLabel("Modul")
        label_z = QLabel("Zähnezahl")

        self. edit_m = QLineEdit()
        self. edit_z = QLineEdit()
        self.drawing_area = DrawingArea()

        layout.addWidget(button, 0, Qt.AlignTop)
        layout.addWidget(label_m, 0, Qt.AlignTop)
        layout.addWidget(self.edit_m, 0, Qt.AlignTop)
        layout.addWidget(label_z, 0, Qt.AlignTop)
        layout.addWidget(self.edit_z, 0, Qt.AlignTop)
        layout.addWidget(self.drawing_area, 1)

        button.clicked.connect(self.calculate_gear)


    @Slot()
    def calculate_gear(self):
        gear_modul = float(self.edit_m.text())
        teeth_count = float(self.edit_z.text())
        self.drawing_area._evol_int_points = []
        gw = GearWheel(gear_modul, teeth_count)
        gw.calculate_basics()
        gw.calculate_complete_tooth()
        self.drawing_area._evol_int_points = gw.evol_int_points
        self.drawing_area._bounding_box = gw.bounding_box
        self.drawing_area.update()


def main():
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

Die globale Frage ist also, was bei diesen beiden Funktionen wirklich geschieht:

Code: Alles auswählen

        painter.translate(center_x, center_y)
        painter.scale(scale_quotient, scale_quotient)
Wenn man diese vertauscht, wirkt sich das noch extremer aus. In der Doku ist leider überhaupt nicht beschrieben, wie diese zusammen wirken.

Wenn man zuerst translate ausführt, verschiebt man sozusagen nur den Anzeigebereich. Wird dann bei scale die Pixelmenge des Fensters erneut vergrößert/verkleinert, oder werden die Koordinaten von dem, was ich zeichne, skaliert.

Wenn man zuerst scale ausführt, geschieht der translate dann zu 100% auf die Pixel, oder werden diese kleiner/größer skaliert?

Meine Vorgehensweise ist, dass ich eine "bounding_box" um meine berechneten Punkte herum erzeuge, diese mit der aktuellen Fenstergröße vergleiche und dementsprechend verschiebe und skaliere. Da meine Y-Werte sehr groß sind (d.h. über 2000), muss ich das Zentrum auf über 2000 Pixel nach Unten setzen. Gebe ich da eine Skalierung drauf, liege ich bei falscher Berechnung ganz schnel weit davon weg (z.B. 1500), was dann außerhalb des Fensters liegt.
Benutzeravatar
sparrow
User
Beiträge: 4230
Registriert: Freitag 17. April 2009, 10:28

Es wird das interne Koordinatensystem transformiert und skaliert. So als hättest du ein Bild in einem Bimdbetrachter und tust damit Dinge.
Das kann aber nur funktionieren, solange du nicht selbst Dinge mir dem Bild währenddessen tust. Also irgendwelche Skalierungen selbst vornehmen. Dafür sind ja eben diese Funktionalitäten vorhanden.

Hier ist das ganz gut erklärt.
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

sparrow hat geschrieben: Samstag 11. Mai 2024, 08:51 Es wird das interne Koordinatensystem transformiert und skaliert. So als hättest du ein Bild in einem Bimdbetrachter und tust damit Dinge.
Das kann aber nur funktionieren, solange du nicht selbst Dinge mir dem Bild währenddessen tust. Also irgendwelche Skalierungen selbst vornehmen. Dafür sind ja eben diese Funktionalitäten vorhanden.

Hier ist das ganz gut erklärt.
Danke für den Hinweis und den Link. Habe daraufhin nochmal genau überlegt (und auf Papier rumgemalt) und ein kleines Testporgramm geschrieben, was jetzt genau das macht, was ich wollte:

Code: Alles auswählen

#!/usr/bin/env python

import sys

from PySide6.QtCore import QSize, Qt, QPoint
from PySide6.QtGui import QPainter, QPen
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget


class DrawingArea(QWidget):
    def __init__(self, evol_points):
        super().__init__()
        self.setMinimumSize(QSize(302, 302))
        self.evol_points = evol_points

    def paintEvent(self, event):

        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        g = self.geometry()
        
        b_box_top = 2150
        b_box_left = 1850
        b_box_width = 300

        scaling = g.height() / b_box_width
        x_center = b_box_left * scaling *(-1)
        y_center = b_box_top * scaling

        painter.translate(QPoint(x_center, y_center))
        painter.scale(scaling, scaling)

        painter.setPen(QPen(Qt.GlobalColor.red, 10 / scaling))

        for evol in self.evol_points:
            painter.drawPoint(QPoint(evol.x(), evol.y() *(-1)))

        painter.end()
        return super().paintEvent(event)


class Window(QWidget):
    def __init__(self, evol_points):
        super().__init__()
        layout = QVBoxLayout(self)
        layout.addWidget(QPushButton("Start"))
        layout.addWidget(DrawingArea(evol_points))

def main():
    app = QApplication(sys.argv)
    evol_points = [QPoint(1850, 2000), QPoint(2150, 2000), QPoint(2000, 1850), QPoint(2000, 2150)]
    window = Window(evol_points)
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

Hallo Zusammen,

untenstehendes Programm macht jetzt das, was es soll und ich habe verrsucht, alles etwas zu trennen. Allerdings denke ich, dass es an manchen Stellen zu umständlich ist. In main.py erstelle ich GUI und controller.Im conroller selbst die Instanzen des models. Den controller gebe ich der GUI mit auf den Weg, damit diese wiederum über den controller an das model heran kommt und auch seinerseits nach Button Click die funktion im controller aufrufen kann.

Da es in der View anfänglich noch keine Bounding_Box gibt (da sie ja erst nach der Berechnung existiert)

Code: Alles auswählen

class DrawingArea(QWidget):
    def __init__(self, controller):
        super().__init__()
        self.setMinimumSize(QSize(400, 350))
        self._controller = controller
        self._int_points = []
        self._bounding_box = BoundingBox()
weise ich hier eine leere Instanz zu, die ich später ersetze. Das führt aber dazu, dass es hier:

Code: Alles auswählen

       scale_quotient = g.height() / self._bounding_box.get_height()
zu division by zero führt, was logisch ist, da es hier noch keine Werte gibt, der paintEvent aber von Beginn an los läuft.

Wenn ich hier @property verwende:

Code: Alles auswählen

class IntPointModel():
    def __init__(self):
        self._int_points = []

    @property
    def get_int_points(self):
        return self._int_points
kommt der Fehler

Code: Alles auswählen

TypeError: 'list' object is not callable
Wer könnte Tipps geben, wie das besser geht, z.B. mit Signals & Slots, bzw ist das überhaupt eingermaßen idiomatisch?

Code: Alles auswählen

# main.py
#!/usr/bin/env python

import sys

from PySide6.QtCore import QSize, Qt, QPoint
from PySide6.QtGui import QGradient, QPainter, QPen
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from view import Window
from controller import Controller

def main():
    controller = Controller()
    app = QApplication(sys.argv)
    window = Window(controller)
    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

Code: Alles auswählen

#controller.py
#!/usr/bin/env python

import sys

from PySide6.QtCore import QSize, Qt, QPoint, Slot, Signal
from PySide6.QtGui import QGradient, QPainter, QPen, QBrush
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget, QLabel, QLineEdit

from GearWheel import GearWheel
from model import FloatingPointModel, IntPointModel, BoundingBox

class Controller():
    def __init__(self):
        self._floating_point_model = FloatingPointModel()
        self._int_point_model = IntPointModel()
        self._bounding_box = BoundingBox()

    # @property
    def get_int_point_model(self):
        return self._int_point_model
    
    # @property
    def get_bounding_box(self):
        return self._bounding_box

    def do_all_the_work(self, gear_modul, teeth_count, drawing_area):

        self._floating_point_model.clear_floating_points()
        self._int_point_model.clear_int_points()

        gw = GearWheel(gear_modul, teeth_count, self._floating_point_model)
        gw.calculate_basics()
        gw.calculate_complete_teeth()
        self._int_point_model.create_int_points(self._floating_point_model.get_floating_points())
        self._bounding_box.create(self._int_point_model.get_int_points())
        drawing_area.get_and_update()

Code: Alles auswählen

#model.py
#!/usr/bin/env python

from PySide6.QtCore import QPoint, Slot, Signal
import sys

class FloatingPointModel():
    def __init__(self):
        self._floating_points = []

    def get_floating_points(self):
        return self._floating_points

    def append_floating_point(self, x):
        self._floating_points.append(x)

    def clear_floating_points(self):
        self._floating_points = []

class IntPointModel():
    def __init__(self):
        self._int_points = []

    # @property
    def get_int_points(self):
        return self._int_points
    
    def append_int_point(self, x, y):
        self._int_points.append(QPoint(x, y))

    def clear_int_points(self):
        self._int_points = []

    def create_int_points(self, floating_points):
         int_scale_factor = 100.0
         for evol in floating_points:
             x, y = evol
             self._int_points.append(QPoint(x * int_scale_factor, y * int_scale_factor)) 

class BoundingBox():
    def __init__(self):
        self._bb_left_x = 0
        self._bb_width_x = 0
        self._bb_top_y = 0
        self._bb_height_y = 0

    # @property
    def get_left(self):
        return self._bb_left_x
    
    # @property
    def get_width(self):
        return self._bb_width_x
    
    # @property
    def get_top(self):
        return self._bb_top_y
    
    # @property
    def get_height(self):
        return self._bb_height_y

    def create(self, int_points):
        x_min = 10000
        x_max = -10000
        y_min = 10000
        y_max = -10000
        boundary = 10

        for evol in int_points:
            x = evol.x()
            y = evol.y()
            x_min = min(x_min, x)
            x_max = max(x_max, x)
            y_min = min(y_min, y)
            y_max = max(y_max, y)
        x_min -= boundary
        x_max += boundary
        y_min -= boundary
        y_max += boundary

        self._bb_left_x = x_min
        self._bb_top_y = y_max
        self._bb_width_x = x_max - x_min
        self._bb_height_y = y_max - y_min

Code: Alles auswählen

#view.py
#!/usr/bin/env python

import sys

from PySide6.QtCore import QSize, Qt, QPoint, Slot, Signal
from PySide6.QtGui import QGradient, QPainter, QPen, QBrush
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget, QLabel, QLineEdit

from controller import Controller
from model import IntPointModel, BoundingBox

class DrawingArea(QWidget):
    def __init__(self, controller):
        super().__init__()
        self.setMinimumSize(QSize(400, 350))
        self._controller = controller
        self._int_points = []
        self._bounding_box = BoundingBox()

    def get_and_update(self):
        int_model = self._controller.get_int_point_model()
        self._int_points = int_model.get_int_points()
        self._bounding_box = self._controller.get_bounding_box()
        self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        g = self.geometry()

        scale_quotient = g.height() / self._bounding_box.get_height()
        center_x = self._bounding_box.get_left() * scale_quotient *(-1)
        center_y = self._bounding_box.get_top() * scale_quotient
        
        painter.translate(QPoint(center_x, center_y))
        painter.scale(scale_quotient, scale_quotient)
 
        painter.setPen(QPen(Qt.GlobalColor.red, 5 / scale_quotient))
        for evol_point in self._int_points:
            painter.drawPoint(QPoint(evol_point.x(), evol_point.y() *(-1) ))

        painter.end()
        return super().paintEvent(event)

class Window(QWidget):
    def __init__(self, controller):
        super().__init__()
        self._controller = controller

        layout = QVBoxLayout(self)

        button = QPushButton("Start")
        label_m = QLabel("Modul")
        label_z = QLabel("Zähnezahl")

        self. edit_m = QLineEdit()
        self. edit_z = QLineEdit()
        self.drawing_area = DrawingArea(self._controller)

        layout.addWidget(button, 0, Qt.AlignTop)
        layout.addWidget(label_m, 0, Qt.AlignTop)
        layout.addWidget(self.edit_m, 0, Qt.AlignTop)
        layout.addWidget(label_z, 0, Qt.AlignTop)
        layout.addWidget(self.edit_z, 0, Qt.AlignTop)
        layout.addWidget(self.drawing_area, 1)

        button.clicked.connect(self.start_calculations)

        # Testwerte
        self.edit_m.setText("1.0")
        self.edit_z.setText("23")

    @Slot()
    def start_calculations(self):
        gear_modul = float(self.edit_m.text())
        teeth_count = float(self.edit_z.text())
        self._controller.do_all_the_work(gear_modul, teeth_count, self.drawing_area)

def main():
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

Code: Alles auswählen

#GearWheel.py
#!/usr/bin/env python

from math import pi,sin, cos, tan, acos
from PySide6.QtCore import QPoint
from model import FloatingPointModel

ALPHA_0 = 20.0 * pi / 180.0     # Normaleingriffswinkel rad (= 20° deg)

class GearWheel():
    def __init__(self, gear_modul, teeth_count, floating_point_model):
        self.gear_modul = gear_modul
        self.teeth_count = teeth_count
        self.floating_point_model = floating_point_model

    def calculate_basics(self):
        # self.gear_modul                                                                                           # Modul
        # self.teeth_count                                                                                          # Zähnezahl
        self.partial_diameter = self.teeth_count * self.gear_modul                                                  # Teilkreis Durchmesser
        self.circumferential_division = self.gear_modul * pi                                                        # Umfangsteilung
        self.circumferential_angle_rad = self.circumferential_division / (self.partial_diameter / 2.0)              # Umfangswinkel Radian
        self.circumferential_angle_deg = self.circumferential_angle_rad / pi * 180.0                                # Umfangswinkel Degree
        self.tooth_head_gap = self.gear_modul * 0.167                                                               # Zahnkopfspiel
        self.foot_circle_diameter = self.partial_diameter - 2.0 * (self.gear_modul + self.tooth_head_gap)           # Fusskreis Durchmesser
        self.head_circle_diameter = self.partial_diameter + 2.0 * self.gear_modul                                   # Kopfkreis Durchmesser
        self.head_circle_diameter_with_gap = self.partial_diameter + 2.0 * (self.gear_modul + self.tooth_head_gap)  # Kopfkreis Durchmesser + Spiel
        self.base_circle_diameter = self.partial_diameter * cos(ALPHA_0)                                            # Grundkreis Durchmesser
        self.tooth_head_height = self.gear_modul                                                                    # Zahnkopf Höhe
        self.tooth_foot_height = self.gear_modul + self.tooth_head_gap                                              # Zahnfuss Höhe
        self.tooth_height_over_all = 2.0 * self.gear_modul + self.tooth_head_gap                                    # Zahn Höhe insgesamt
 
        print(f'Modul={self.gear_modul}\nAnzahl Zähne={self.teeth_count}\nTeilkreis_Durchmesser={self.partial_diameter}')
        print(f'Umfangsteilung={self.circumferential_division}\nUmfangswinkel_Radian={self.circumferential_angle_rad}')
        print(f'Zahnkopfspiel={self.tooth_head_gap}\nUmfangswinkel_Degree={self.circumferential_angle_deg}')
        print(f'Fusskreis_durchmesser={self.foot_circle_diameter}\nKopfkreis_Durchmesser={self.head_circle_diameter}')
        print(f'Kopfkreis_Durchmesser + Spiel={self.head_circle_diameter_with_gap}')
        print(f'Grundkreis_Durchmesser={self.base_circle_diameter}\nZahnkopf_Höhe={self.tooth_head_height}')
        print(f'Zahnfuss_Höhe={self.tooth_foot_height}\nZahn_Höhe={self.tooth_height_over_all}')
        print(f'Minimaler Section Wert={self.foot_circle_diameter}\nMaximaler Section Wert={self.head_circle_diameter}')

    def calculate_evolvente_thickness(self, section):
        if section < self.base_circle_diameter or section > self.head_circle_diameter: return

        s_0 = self.gear_modul * pi / 2.0
        d_0 = self.partial_diameter
        gamma_0 = tan(ALPHA_0) - ALPHA_0
        alpha = acos(d_0 / section * cos(ALPHA_0))
        gamma = tan(alpha) - alpha
        s = section * (s_0 / d_0 + gamma_0 - gamma)
        return s
    
    def calculate_complete_teeth(self):

        step = 0.1
        section = self.base_circle_diameter
        while(section <= self.head_circle_diameter):
            evol_point = self.calculate_evolvente_thickness(section)
            self.floating_point_model.append_floating_point((evol_point / 2.0, section / 2.0))
            self.floating_point_model.append_floating_point((evol_point / 2.0 *(-1) , section / 2.0))
            section += step

def main():
    pass

if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13187
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Also ich hätte ja als erstes mal was am `GearModul`-Objekt auszusetzen. 🤓 Nach `__init__()` sollte ein Objekt benutzbar sein. Also beispielsweise sollten danach alle Attribute existieren. Man sollte nicht erst Methoden aufrufen müssen, und das auch noch mehr als eine, und das auch noch in der richtigen Reihenfolge, damit ein Objekt alle Attribute hat, die man da mal abfragen möchte.

`QPoint` und `FloatingPointModel` werden importiert, aber nirgends verwendet. Wobei sowohl `FloatingPointModel` als auch `IntPointModel` komisch sind. Irgendwie hätte man da vielleicht auch einfach Listen für nehmen können. Und auch einen Controller würde ich nur schreiben wenn der tatsächlich sinnvoll ist, also wenn da tatsächlich was austauschbar ist und der nicht einfach zwischen *einem* View und *einem* Model sitzt. *Dafür* muss man das nicht unnötig kompliziert machen.

Die beiden Model-Klassen sind auch irgendwie ”assymetrisch”. Eine hat Punkte als Tupel mit Gleitkommazahlen, die andere `QPoint`. Warum nicht beide mit Tupeln oder beide mit Qt-Klassen, denn für Punkte mit Gleitkommazahlen gibt es ja `QPointF`. Und im Grunde machen beide Klassen das selbe und unterscheiden sich fast nur durch die Namen. Wobei im Namen von Methoden nicht dauernd Teile vom Klassennamen vorkommen sollten. Statt `add_int_points()` reicht einfach `add_points()` oder gar nur `add()`, denn dass das Punkte sind, ergibt sich ja schon aus dem Typ `IntPointModel`.

Zur Fehlermeldung: Wenn Du ein `property()` erstellst dann reicht der Name zum Zugriff. Das ist ja der Witz daran, dass das nach aussen wie ein einfacher Attributzugriff aussieht. Also ``model.get_int_points`` liefert dann bereits die Liste. Wenn da noch Aufrufklammern stehen, dann wird versucht die Liste aufzurufen, was genau zu der Fehlermeldung führt. Und das Attribut sollte dann nicht mehr wie eine Methode heissen, sondern wie ein Datenattribut, also nur noch `model.int_points`.

Zurück zu `GearWheel`. Die `calculate_complete_teeth()`-Methode sollte nicht ein irgendwo anders erzeugtes Containerobjekt befüllen, sondern das selbst erstellen und als Rückgabewert liefern. Denn sonst passiert ja Murks wenn dieser Container nicht wirklich leer ist. Zum Beispiel wenn man die Methode zweimal aufruft.

``section += step`` ist ein Fehler. Gleitkommazahlen! 0.1 ist etwas grösser als 1/10. Aber wenn man das 10 mal aufaddiert, kommt eine Zahl heraus die etwas kleiner als 1 ist:

Code: Alles auswählen

In [580]: step = 0.1

In [581]: format(step, ".55f")
Out[581]: '0.1000000000000000055511151231257827021181583404541015625'

In [582]: total = 0

In [583]: for _ in range(10):
     ...:     total += step
     ...: 

In [584]: total
Out[584]: 0.9999999999999999
Eine ``while``-Schleife die solange 0.1 aufaddiert bis 1 erreicht ist, würde als Endlos laufen und eine die solange aufaddiert bis ≥1 erreicht ist, würde 11(!) mal durchlaufen.

Der Test am Anfang von `calculate_evolvente_thickness()` ist kaputt, oder wenn man das so macht der Code der das aufruft, dann aber versucht mit dem `None` weiter zu rechnen das dort eventuell zurückgegeben wird.

Ich habe jetzt nicht im Rest des Programms nachgeschaut ob da noch mehr von den ”basics”-Attributen benutzt werden und habe einfach mal alles rausgeworfen was nicht in der Klasse selbst verwendet wird (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python
from math import pi, cos, tan, acos


ALPHA_0 = 20 * pi / 180  # Normaleingriffswinkel rad (= 20° deg)


class GearWheel:
    def __init__(self, gear_modul, teeth_count):
        self.gear_modul = gear_modul
        self.teeth_count = teeth_count

    @property
    def partial_diameter(self):
        return self.teeth_count * self.gear_modul

    @property
    def base_circle_diameter(self):
        return self.partial_diameter * cos(ALPHA_0)

    @property
    def head_circle_diameter(self):
        return self.partial_diameter + 2 * self.gear_modul

    def _calculate_evolvente_thickness(self, section):
        assert (
            self.base_circle_diameter <= section <= self.head_circle_diameter
        )
        s_0 = self.gear_modul * pi / 2
        d_0 = self.partial_diameter
        gamma_0 = tan(ALPHA_0) - ALPHA_0
        alpha = acos(d_0 / section * cos(ALPHA_0))
        gamma = tan(alpha) - alpha
        return section * (s_0 / d_0 + gamma_0 - gamma)

    def calculate_complete_teeth(self):
        points = []
        step = 0.1
        point_count = (
            self.head_circle_diameter - self.base_circle_diameter
        ) / step
        for section_index in range(point_count):
            section = self.base_circle_diameter + section_index * step
            evol_point = self._calculate_evolvente_thickness(section)
            points.append((evol_point / 2, section / 2))
            points.append((evol_point / -2, section / 2))

        return points
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 137
Registriert: Dienstag 29. Dezember 2009, 00:09

Hallo __blackjack__:
Erstmal vielen Dank für Deine hilfreichen Anmerkungen.

IntPointModel hat nur den Zweck, gut skalierte Zahlenwerte zu haben, um Pixelmäßig zeichnen zu können. Die Werte ansich sind ja sehr klein (im aktuellen Beispiel etwa nur +/-25). Wie ist das mit QPointF beim Zeichnen? Werden da die Nachkommastellen einfach abgeschnitten (Es gibt ja keine halben Pixel)?
Dann könnte ich mir das mit IntPointModel ganz sparen und tatsächlich einfach beim Zeichnen die float Werte skalieren.
Controller in dem Fall ersparen heißt also, dass ich per Button.click die Liste von GearWheel anfordere und direkt verwende?!

@property, das wird in dem Fall also zur Laufzeit bei Abruf immer wieder berechnet. Was mache ich, wenn es permanent vorhanden sein soll und auch beschreibar sein soll, d.h. getter / setter z.b. aus C# nachbilden?

Dieses Konstrukt:

Code: Alles auswählen

point_count = (
            self.head_circle_diameter - self.base_circle_diameter
        ) / step
funktioniert allerdings nicht, da es sich um float Werte handelt:

Code: Alles auswählen

(25 - 21.612930) / 0.1
Daher geht das nicht als Laufindex, die Frage ist dann wirklich, wie man die letzte position genau "trifft". Eigentlich ja nur, indem man den letzten Wert nochmals mit redundantem Code getrennt berechnet.
Übrigens was die (binär dargestellte) Mantisse bei 0.1 betrifft, da hat soweit ich weiß z.B. Clojure einen Vorteil, da es mit rationalen Zahlen rechnen kann und die Frage ist, ob es dazu Pythonseitig was Ähnliches gibt.

Ich werde das ganze nochmal anhand Deiner Erklärungen überarbeiten. Nochmals besten Dank!
Sirius3
User
Beiträge: 17792
Registriert: Sonntag 21. Oktober 2012, 17:20

Weil es in Python @property gibt, braucht man keine trivialen Getter und Setter. Wenn es also permanent und änderbar sein soll, dann nimmt man einfach ein Attribut.

Zum Beispiel Deine Boundingbox-Klasse. Zum einen ist das bb-Päfix und das x/y in den Attributen überflüssig. Dass es ein Attribut einer BoundBox ist, sieht man alleine daran, dass das Objekt ja vom Typ BoundingBox ist.
Die ganzen trivialen get-Methoden kann man sich dann bei Attributnamen left, width, top und height sparen. __init__ macht nichts sinnvolles, und create sollte dem Namen nach eine BoundingBox erzeugen, und nicht Attibute ändern.
Das was in create steht, sollte also entweder nach __init__ wandern, oder man benutzt hier eine classmethod.

Inhaltlich ist an der create-Methode schlecht, dass y_min, x_max, etc. mit Dummy-Werten belegt wird. Warum gerade 10000? Besser wäre es, wenn es diese Zahlen gar nicht bräuchte.
Das ganze sähe also ungefähr so aus:

Code: Alles auswählen

class BoundingBox():
    def __init__(self, left, width, top, height):
        self.left = left
        self.width = width
        self.top = top
        self.height = height

    @classmethod
    def create(cls, points, boundary=10):
        x_values = [point.x() for point in points]
        y_values = [point.y() for point in points]
        x_min = min(x_values) - boundary
        x_max = min(x_values) + boundary
        y_min = min(y_values) - boundary
        y_max = min(y_values) + boundary
        return cls(x_min, y_max, x_max - x_min, y_max - y_min)
Benutzeravatar
__blackjack__
User
Beiträge: 13187
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Zur BoundingBox vielleicht noch der Hinweis das es doch `QRect` gibt. Und müssen das eigentlich Punkte in einer eigenen Datenstruktur sein? Es gibt ja beispielsweise `QPolygon` und `QPolygonF`. Die kann man zeichnen und die haben eine `boundingRect()`-Methode.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Antworten