@Erhy: Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).
Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Namen sollten nicht abgekürzt oder nummeriert werden. Wenn man `rect0` gleich `plot_area` nennen würde, bräuchte man keinen Kommentar schreiben was `rect0` eigentlich bedeuten sollte, sondern hätte überall wo der Name verwendet wird die Bedeutung deutlich lesbar im Namen stehen. Ähnliches für `r0` vs. `zero`. Wobei man da auch ein 0 hätte nehmen können. Oder `ci` vs. `circle`. `resul` statt `result`? Tatsächlich um *ein* Zeichen zu ”sparen”?
Funktionen und Methoden sollten alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen. Sonst sind sie nicht unabhängig von ihrer Umgebung test- und wiederverwendbar. Auch das verschieben in ein anderes Modul wird so zu einer unnötig aufwändigen Aufgabe.
Man sollte einer Funktion keine Liste übergeben die in der Funktion dann gefüllt wird. Falls man so etwas macht, sollte man sehr deutlich dokumentieren das die Liste in der Funktion verändert wird. Besser ist es die Funktion die Liste erstellen und als Rückgabewert liefern lassen. Oder eine Generatorfunktion zu schreiben, dann kann der Aufrufer entscheiden ob er tatsächlich eine Liste braucht, oder nur etwas zum iterieren.
Die Zuweisungen an `rect0j` machen keinen Sinn. Das führt ohne Not einfach nur noch einen weiteren Namen ein, der zudem noch nicht einmal zur Verständlichkeit beiträgt.
Funktionen sollte man nicht schachteln sofern man nicht tatsächlich ein Closure benötigt, oder die innere Funktion wirklich sehr kurz und simpel ist. Man kann die innere Funktion nicht separat testen und der Leser muss erst einmal schauen ob es sich um ein Closure handelt oder nicht.
`twoIntersectionWithBorder()` ist ein nicht wirklich offensichtlicher Name für eine Funktion die aus zwei Sympy-Punkten eine Matplotlib-Linie erstellt.
`p1` und `p2` lassen sich auch einfacher ermitteln, weil kein Grund besteht die Werte einzeln in neue Listen zu kopieren:
Code: Alles auswählen
p1 = [points[0].args[0], points[0].args[1]]
p2 = [points[1].args[0], points[1].args[1]]
# ->
p1 = points[0].args
p2 = points[1].args
Die gesamte Funktion kann man ziemlich simpel auch so schreiben:
Code: Alles auswählen
def make_2d_line_from_points(points):
return mlines.Line2D(
[point.args[0] for point in points],
[point.args[1] for point in points],
)
Da direkt nach jedem Aufruf der Funktion die Linienstärke festgelegt wird, sollte man das vielleicht auch gleich mit *in* die Funktion nehmen.
Die Funktion wird auch nicht immer verwendet wenn man sie verwenden könnte, was zu sehr ähnlichem Code führt.
Datentypen mit `type()` und `str()` in Zeichenketten umzuwandeln um die dann zu vergleichen ist falsch. Es gibt keine wirkliche Garantie wie diese Zeichenketten konkret aussehen. Wenn man Typtests macht, dann mit `isinstance()` – was auch Vererbung berücksichtigt. Noch als Anmerkung dazu: Typtests sind hier okay, aber generell ein „code smell“ in objektorientierten Programmiersprachen.
Ein paar von den `args[]`-Zugriffen kann man verständlicher schreiben wenn man den dazugehörigen Attributnamen verwendet.
Fast alles an Werten in Listen umkopieren und in `float()` wandeln ist überflüssig.
An das Ende der ``if``/``elif``-Kaskade gehört ein ``else`` das auf unbekannte Typen reagiert.
Die Konvertierungsfunktion würde ich noch mit `functools.singledispatch` aufteilen und die `main()` ist für meinen Geschmack auch schon zu lang, aber das hier ist der Zwischenstand:
Code: Alles auswählen
#!/usr/bin/env python3
# PlotSympyGeometryWithMatPlotLibExample.py by Erhy
from matplotlib import patches
from matplotlib import lines as mlines
from matplotlib import pyplot as plt
from sympy import Rational, pi as PI
from sympy import geometry as geom
def make_2d_line_from_points(points, linewidth):
return mlines.Line2D(
[point.x for point in points],
[point.y for point in points],
linewidth=linewidth,
)
def convert_geometry_to_artists(plot_area, geometry_entities):
#
# TODO Use `functools.singledispatch` to break this up into smaller, self
# contained functions, so there's no chance of leaking values from one
# type/loop iteration to another.
#
for entity in geometry_entities:
if isinstance(entity, geom.line.Segment2D):
points = geom.intersection(plot_area, entity)
if len(points) == 2:
yield make_2d_line_from_points(points, 2)
elif len(points) == 1:
#
# Find the other point of the segment, which is inside the
# borders.
#
point_b = next(
point
for point in entity.args
if plot_area.encloses_point(point)
)
yield make_2d_line_from_points([points[0], point_b], 2)
else:
if all(map(plot_area.encloses_point, entity.args)):
# Completely inside the borders.
yield make_2d_line_from_points(entity.args, linewidth=2)
elif isinstance(entity, geom.line.Line2D):
points = geom.intersection(plot_area, entity)
if len(points) == 2:
yield make_2d_line_from_points(points, 1)
elif isinstance(entity, geom.line.Ray2D):
points = geom.intersection(plot_area, entity)
if len(points) == 2:
yield make_2d_line_from_points(points, 1)
elif len(points) == 1:
yield make_2d_line_from_points([points[0], entity.source], 1)
elif isinstance(entity, geom.ellipse.Circle):
yield patches.Circle(
entity.center, entity.radius, linewidth=1, fill=False
)
else:
raise ValueError(f"unknown geometry entity {entity!r}")
def main():
#
# Plot area for conversion from sympy.geometry elements to MatPlotLib
# artists.
#
plot_max_x = Rational("0.300")
plot_max_y = Rational("0.255")
plot_area = geom.Polygon(
(0, 0), (plot_max_x, 0), (plot_max_x, plot_max_y), (0, plot_max_y)
)
#
# Example for visual check of angle computation.
#
point_a = geom.Point([Rational("0.04"), Rational("0.015")])
point_b = geom.Point([Rational("0.135"), Rational("0.05")])
point_c = geom.Point([Rational("0.210"), Rational("0.140")])
horizontal_line = geom.Line(geom.Point([0, 0]), geom.Point([1, 0]))
segment_a = geom.Segment(point_a, point_b)
segment_b = geom.Segment(point_b, point_c)
min_segment_length = min(segment_a.length, segment_b.length)
circle = geom.Circle(point_b, min_segment_length)
# Line for visual test.
middle_line = geom.Line(
segment_a.intersection(circle)[0], segment_b.intersection(circle)[0]
).parallel_line(point_b)
# Angle computation.
segment_a_angle = horizontal_line.smallest_angle_between(segment_a)
segment_b_angle = horizontal_line.smallest_angle_between(segment_b)
angle_stretch = segment_a_angle + ((segment_b_angle - segment_a_angle) / 2)
gradient_angle = (
PI + segment_a_angle + ((angle_stretch - segment_a_angle) / 2)
)
gradient_ray = geom.Ray(point_b, angle=gradient_angle)
plot_artists = convert_geometry_to_artists(
plot_area, [segment_a, segment_b, circle, middle_line, gradient_ray]
)
figure = plt.figure(figsize=(7, 7))
axis = figure.add_subplot(1, 1, 1, aspect=1)
axis.set_xlim(0, float(plot_max_x))
axis.set_ylim(0, float(plot_max_y))
for artist in plot_artists:
axis.add_artist(artist)
plt.show()
if __name__ == "__main__":
main()