Kreisel-Umlauf: Abfolge ueber BB-Halbachsen statt Winkel um Mittelpunkt
compute_kreisel_umlauf sortierte Punkte bisher per atan2 um den Kapsel-Mittelpunkt. Auf der langen Geraden (dx >> dy) liegen alle echten Winkel in einem winzigen Band nahe 0 Grad - ein grosser, asymmetrischer Block wie BTMT-Beladung/-Entladung hat einen Bbox- Mittelpunkt, der deutlich weiter quer zur Achse absteht als der Belt-Radius, was seinen Winkel unverhaeltnismaessig aufblies und ihn mehrere eng benachbarte Punkte ueberholen liess, obwohl er entlang der Achse klar der letzte ist (realer Fall: HundM05, BTMT-Entladung "0023", landete zwischen 0039/0016 statt hinter 0018/0020). Neu: Punkt in lokale Achsrichtung drehen und auf die BB-Halbachsen (halbe Achslaenge, Radius) normieren; welche der beiden Komponenten ueberwiegt, legt nur noch die Seite fest (Stirnseite/Langseite) - auf den Langseiten bestimmt ausschliesslich die Achsrichtung die Position, der quer abstehende Anteil kippt bestenfalls die Seiten-Zuordnung, verzerrt aber nicht mehr die Reihenfolge selbst. Mit den realen HundM05-Koordinaten verifiziert; test_export_kreisel_umlauf.py und test_export_graph.py weiterhin gruen (20/20), keine neuen Regressionen in der breiteren Suite. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
d0d112e740
commit
0e4ea7e148
+53
-15
@@ -1521,26 +1521,64 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
acx = (cap["a"][0] + cap["b"][0]) / 2.0
|
||||
acy = (cap["a"][1] + cap["b"][1]) / 2.0
|
||||
achs_winkel = math.atan2(cap["b"][1] - cap["a"][1], cap["b"][0] - cap["a"][0])
|
||||
cos_achs, sin_achs = math.cos(achs_winkel), math.sin(achs_winkel)
|
||||
# Halbe Achslaenge (Mitte -> a bzw. b) und Radius der Kapsel = die
|
||||
# beiden Halbachsen der Kreisel-BB im lokalen (gedrehten) Koordinaten-
|
||||
# system - siehe kreisel_bounds oben, dieselbe "maximale Ausdehnung".
|
||||
halblaenge = math.hypot(cap["b"][0] - cap["a"][0], cap["b"][1] - cap["a"][1]) / 2.0
|
||||
radius = cap["r"]
|
||||
|
||||
uzs = (drehrichtung or "UZS").strip().upper() != "GUZ"
|
||||
|
||||
def _winkel(pxy):
|
||||
# Rohwinkel relativ zur Achsrichtung, auf [0, 2*pi) normiert IN
|
||||
# UMLAUFRICHTUNG. Vorher (reine Vorgaenger-/Nachfolger-Ermittlung
|
||||
# ohne Startpunkt-Anspruch) wurde UZS durch reverse=True auf dem
|
||||
# rohen mathematischen Winkel erreicht (= "absteigend sortieren");
|
||||
# absteigend sortieren auf theta ist aequivalent zu aufsteigend
|
||||
# sortieren auf -theta - darum wird HIER fuer UZS negiert (NICHT
|
||||
# fuer GUZ, das dem unveraenderten mathematischen Winkel bereits
|
||||
# aufsteigend entspricht). Ohne dieses Vorzeichen liefe der UZS-
|
||||
# Umlauf in die falsche (zu GUZ spiegelverkehrte) Richtung - siehe
|
||||
# Abfolge ueber die BB des Kreisels statt ueber einen Winkel um den
|
||||
# Mittelpunkt: Punkt zuerst ins lokale (um achs_winkel gedrehte)
|
||||
# Koordinatensystem der Kreiselachse bringen (lx = Achsrichtung,
|
||||
# ly = quer dazu), dann auf die BB-Halbachsen normieren
|
||||
# (u = lx/halblaenge, v = ly/radius). Je nachdem, welche der beiden
|
||||
# Groessen (u oder v) betragsmaessig ueberwiegt, liegt der Punkt auf
|
||||
# der rechten/linken Stirnseite (u dominiert) oder der oberen/
|
||||
# unteren Langseite (v dominiert) der BB - dort bestimmt NUR die
|
||||
# jeweils andere (die entlang der Seite laufende) Koordinate die
|
||||
# Position, der ueberwiegende Anteil legt nur noch die Seite fest.
|
||||
# Ein grosser, asymmetrischer Block (z.B. BTMT-Beladung/-Entladung)
|
||||
# hat einen Bbox-Mittelpunkt, der deutlich weiter quer zur Achse
|
||||
# absteht als der Belt-Radius - beim reinen Winkel-um-Mittelpunkt
|
||||
# (frueherer Ansatz) blies das seinen Winkel unverhaeltnismaessig
|
||||
# auf und liess ihn mehrere eng benachbarte Punkte in der engen
|
||||
# Sortierung ueberholen, obwohl er entlang der Achse klar der
|
||||
# letzte ist (realer Bug-Fall: HundM05, BTMT-Entladung "0023").
|
||||
# Hier wirkt sich dieselbe Ueberhoehung nicht mehr aus, solange sie
|
||||
# nicht die Seiten-Zuordnung selbst umkippt.
|
||||
#
|
||||
# t liegt in [0,4) und laeuft GEGEN den Uhrzeigersinn (mathematische
|
||||
# Standardrichtung) einmal um die BB, beginnend bei t=0 auf der
|
||||
# rechten Stirnseite in Achsrichtung (lx=halblaenge, ly=0) - das
|
||||
# ist derselbe Nullpunkt wie beim frueheren "0 Grad = Achsrichtung".
|
||||
# 0..1 rechte Stirnseite (ly steigend), 1..2 obere Langseite (lx
|
||||
# fallend), 2..3 linke Stirnseite (ly fallend), 3..4 untere
|
||||
# Langseite (lx steigend).
|
||||
dx0 = pxy[1] - acx
|
||||
dy0 = pxy[2] - acy
|
||||
lx = dx0 * cos_achs + dy0 * sin_achs
|
||||
ly = -dx0 * sin_achs + dy0 * cos_achs
|
||||
u = lx / halblaenge if halblaenge > 1e-9 else 0.0
|
||||
v = ly / radius if radius > 1e-9 else 0.0
|
||||
if u >= abs(v):
|
||||
t = v / 2.0
|
||||
elif v >= abs(u):
|
||||
t = 1.0 - u / 2.0
|
||||
elif u <= -abs(v):
|
||||
t = 2.0 - v / 2.0
|
||||
else:
|
||||
t = 3.0 + u / 2.0
|
||||
|
||||
# UZS (Uhrzeigersinn, Default) negiert t (= absteigend sortieren
|
||||
# auf t ist aequivalent zu aufsteigend auf -t) - identisches Muster
|
||||
# wie beim vorherigen Winkel-Ansatz, siehe
|
||||
# tests/test_export_kreisel_umlauf.py::TestKreiselUmlaufStartetBeiNullGrad.
|
||||
# Die Normierung auf [0, 2*pi) sorgt zusaetzlich dafuer, dass
|
||||
# Index 0 nach dem Sortieren der kleinste Winkel >= 0 Grad ist
|
||||
# (der gewuenschte Start bei 0 Grad).
|
||||
roh = math.atan2(pxy[2] - acy, pxy[1] - acx) - achs_winkel
|
||||
roh = -roh if uzs else roh
|
||||
return roh % (2.0 * math.pi)
|
||||
t = -t if uzs else t
|
||||
return t % 4.0
|
||||
|
||||
sortiert = sorted(punkte, key=lambda p: _winkel(p))
|
||||
n = len(sortiert)
|
||||
|
||||
Reference in New Issue
Block a user