Ein primitives Font Design Tool

Plattformunabhängige GUIs mit wxWidgets.
Antworten
vlancer
User
Beiträge: 2
Registriert: Samstag 29. Januar 2011, 17:22

Ich habe ein kleines primitives Fontdesign-tool in drawbot begonnen und den code mittlerweile mehr oder weniger zu wxPython mit Python 2.7 portiert. Die grundlegende Idee im Gegensatz zu anderer Fontsoftware ist, dass ich von den Buchstaben nur die Extrempunkte abspeichere und damit dann die Kurve zeichne.
Später soll es auch möglich sein die Extrema zu verschieben und ich will Slider und ähnliches integrieren.

http://pastebin.com/7ywDymn2
Bild


Mein Problem ist dass es beim durchlaufen der Buchstaben nicht nur Rundungen geben soll, sondern wie zum Beispiel beim S auch eine Gerade dazwischen liegen kann. Deswegen habe ich dann versucht so etwas wie GetPreviousConnection() und getFollowingConnection() zu schreiben. Ich weiß aber nicht so richtig wie ich das jetzt beim Zeichnen integrieren kann.

Vielleicht hat ja mal jemand Zeit sich durch meinen Code durch zu wurschteln und mir ein paar Verbesserungsvorschläge zu geben? Ist sicher ziemlich konfus, aber gerade komme ich irgendwie nicht weiter.
BlackJack

@vlancer: Vorweg ein Fehler den Du ziemlich häufig machst: ``is`` ist nicht zum Vergleichen von beliebigen Werten da. Das Dein Programm überhaupt funktioniert ist im Grunde Zufall. ``is`` testet ob zwei Objekte identisch sind, nicht ob sie den gleichen Wert haben. Man sollte das nur verwenden, wenn man auch ganz sicher ist, dass man auf Objektidentität testen möchte. Im Regelfall ist das nur bei `None` der Fall, weil die Sprachspezifikation garantiert, dass das ein Singleton ist -- es also nur *ein* Exemplar davon geben kann. Bei Wahrheitswerten ist es durchaus möglich, dass es mehr als ein wahres Objekt vom Typ `bool` geben kann. Die wären dann zwar gleich, aber eben nicht identisch.

Explizite Vergleiche mit `True` oder `False` sind aber auch mit ``==`` oder ``!=`` unschön, weil unnötig. Wenn man einen Wahrheitswert `x` hat, dann ergibt ``x == True`` wieder einen Wahrheitswert -- nämlich den *gleichen* den man schon an `x` gebunden hat. Wenn man das Gegenteil feststellen möchte, dann ist ``not x`` einem ``x == False`` oder gar ``x != True`` vorzuziehen.

Anstelle ein `Stroke`-Exemplar als Elternobject in einer `Connection` zu setzen, könntest Du die `Connection`-Exemplare auch mit `next`- und `previous`-Attributen versehen, die jeweils auf den Vorgänger und den Nachfolger verweisen oder auf `None`, falls es ein Endpunkt von einem `Stroke` ist. Das ist sicher effizienter als die aktuelle Position immer mit `index()` zu suchen. Dann braucht man auch die `get*Connection()`-Methoden nicht mehr.

`Connection._parent` sollte `Connection.parent` heissen und die `getParent()`- und `setParent()`-Methoden sind dann überflüssig. Triviale Getter/Setter braucht man in Python nicht -- die Schreibarbeit kann man sich sparen.

`Connection.angle()` könnte man zu einem Property machen. So als Übung. Oder umbenennen, so dass es nicht nach einem Attributnamen klingt.

Damit die `get*Connection()`-Methoden so funktionieren, müsste `Stroke` noch mindestens `__getitem__()` implementieren. Und solltest Du bei dem Weg über ein Elternobjekt bleiben, lassen sich die beiden Methoden auch viel kürzer schreiben. Beispielhaft und ungetestet für `getPreviousConnection()`:

Code: Alles auswählen

    def getPreviousConnection(self):
        i = self.parent.index(self) - 1
        return self.parent[i] if 0 <= i < len(self.parent) else None
Es würde vielleicht Sinn machen auf `Node`-Objekten mehr Operationen zu implementieren. Zum Beispiel um solche Punkte zu addieren oder subtrahieren, oder den Winkel einer Geraden zwischen zwei `Node`\s zu berechnen. Dann würde `Connection.angle()` zum Beispiel zu ``return self.start.angle_to(self.end)`` werden.

`Connection.draw()` ist ziemlich lang. Da könnte man vielleicht ein paar Zeilen kompakter ausdrücken und Funktionen heraus ziehen. Die Bedingung ob Rundungen invloviert ist, lässt sich zum Beispiel unter die 80-Zeichen-Grenze bringen:

Code: Alles auswählen

        if any(x in self.name for x in ['rb', 'lb', 'rt', 'lt']):
Und `inwards` kann man einsparen. Immer wenn man einen Wahrheitswert aufgrund einer Bedingung setzt, braucht man kein ``if``/``else`` denn die Bedingung *selbst* wird ja zu einem einen Wahrheitswert ausgewertet. `inwards` liesse sich also auch so zuweisen:

Code: Alles auswählen

            inwards = (
                (
                    'lt' in self.name
                    and self.start.x >= self.end.x
                    and self.start.y >= self.end.y
                ) or (
                    'rt' in self.name
                    and self.start.x <= self.end.x
                    and self.start.y >= self.end.y
                ) or (
                    'lb' in self.name
                    and self.start.x >= self.end.x
                    and self.start.y <= self.end.y
                ) or (
                    'rb' in self.name
                    and self.start.x <= self.end.x
                    and self.start.y <= self.end.y
                )
            )
Und da es nur einmal benötigt wird, könnte man es auch gleich als ``if``-Bedingung verwenden.

Warum wird in `Stroke.index()` die Ausnahme durch einen "Fehlerwert" ersetzt? Ausnahmen wurden ja gerade erfunden um solche speziellen Werte für Fehler loszuwerden. Du tauscht hier auch nur eine Ausnahme gegen eine andere ein, nämlich an der Stelle wo Du dann versuchst mit dem `None` etwas zu machen was damit nicht geht. Zum Beispiel 1 addieren oder abziehen.
vlancer
User
Beiträge: 2
Registriert: Samstag 29. Januar 2011, 17:22

Danke erstmal dass du dir die Zeit genommen hast, durch den quelltext zu schauen. Ich habe mittlerweile das Gefühl, dass ich da selber nicht mehr durchblicke :D

Die Erklärung und Aufklärung über die Booleans war schon mal sehr gut, das habe ich auch gleich nach deinem Beitrag geändert. Das mit dem "inwards"-ifs macht auch Sinn. Ich hatte auch schon nach nem case/switch-konstrukt in python gesucht, aber das wäre dann wohl so eine Art äquivalent.

Ich habe jetzt die letzten Tage ewig rumprobiert, die getter/setter mit dem zu ersetzen was du mir vorgeschlagen hast, aber ich hab das einfach nicht hinbekommen oder hab die Erklärung schon nicht verstanden. Mein Problem ist vorallem wie schaffe ich es, beim malen nach vorne und nach hinten zu schauen, weil eine Klasse drüber kann ich das. Also ich bekomme es ja hin mit

Code: Alles auswählen

def draw(self,gc):
        for c in self.connections:
            c1=c.getPreviousConnection()
            
            if c1 != None:
                c1.draw(gc)
                print c.getAngle()
zu testen ob die letzte oder erste Connection nicht da ist, aber wie bekomme ich es hin den Winkel beim malen auszulesen. Also ich hätte gerne sowas: self.getPreviousConnection().getAngle() während ich male.

Letztendlich habe ich dann aufgegeben und lieber meine Zeit damit vergeudet Slider einzubauen. Aber irgendwie ist das mit den globalen Variablen, wie ich das gemacht habe, auch nicht das gelbe vom Ei, weil sich dann alles doppelt. Sollte ich dann für diese ganzen Zusammenhänge immer einzelne Funktionen schreiben?

http://pastebin.com/GsSNYc7P


Vielleicht wäre es doch einfacher ich würde einfach noch mal Informatik studieren ... Kann man hier eigentlich auch so etwas wie bezahlte Nachhilfe bekommen?
BlackJack

@vlancer: ``switch``/``case`` gibt es in Python nicht. Wenn man die einzelnen Fallbearbeitungen auf Funktionen abbilden kann, dann setzt man das oft als Dictionary um, bei dem die Werte auf Funktionen abgebildet werden und man sich passende Funktion heraus sucht und aufruft. Das können auch lokale Funktionen sein, wenn man auf lokale Namen zugreifen muss, und auch anonyme Funktionen (``lambda``) falls man sich auf einen Ausdruck beschränken kann.

Mit `previous` und `next` als Attributen und `angle` als Property wird ``self.getPreviousConnection().getAngle()`` ganz einfach zu ``self.previous.angle``.

Wenn Du Attribute für Vorgänger und Nachfolger verwendest, dann müsstest Du die natürlich auch an entsprechender Stelle verbinden. Zum Beispiel in `Stroke` wo Du momentan das `Stroke`-Exemplar als Elternobjekt für `Connection`\s setzt.
Antworten