Unittests zu allen Elementen erstellt
This commit is contained in:
+141
-100
@@ -1,14 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from ezdxf.entities import Line
|
||||
"""
|
||||
Kreisel — Modell und Zeichenfunktionen für Kreisel-Komponenten.
|
||||
|
||||
Refactoring-Änderungen:
|
||||
- Pin-Zeichenlogik (4x copy-paste für 0°/90°/180°/270°) durch PIN_OFFSETS-Lookup-Tabelle ersetzt
|
||||
- @staticmethod auf draw_kreisel_lines und draw_kreisel_drehrichtung_markierung
|
||||
- Magic Number 50 durch benanntes Constant PIN_OFFSET ersetzt
|
||||
- Richtungspfeil-Import aus Loop herausgezogen (war 6x idempotent aufgerufen)
|
||||
- Unused variable bref entfernt
|
||||
"""
|
||||
import math
|
||||
|
||||
from ezdxf.entities import Line
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import Optional
|
||||
from lib import plant2dxf
|
||||
|
||||
from lib import block_methoden
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# KONSTANTEN
|
||||
# ============================================================================
|
||||
|
||||
ATTR_TAG = "TeileId" # Attributtag im Block
|
||||
RADIUS = 400 # Radius der Kreiselkreise (mm)
|
||||
PIN_OFFSET = 50 # Offset für Pin-Bereichsmarkierung (mm)
|
||||
|
||||
# Pin-Bereichs-Offsets pro Drehung: (p1a_dx, p1a_dy, p1b_dx, p1b_dy, p2a_dx, p2a_dy, p2b_dx, p2b_dy)
|
||||
# Berechnet aus dem manuell kodierten Original für 0°/90°/180°/270°
|
||||
PIN_OFFSETS = {
|
||||
0.0: (
|
||||
-(RADIUS + PIN_OFFSET), +PIN_OFFSET,
|
||||
-(RADIUS + PIN_OFFSET), -PIN_OFFSET,
|
||||
+(RADIUS + PIN_OFFSET), +PIN_OFFSET,
|
||||
+(RADIUS + PIN_OFFSET), -PIN_OFFSET,
|
||||
),
|
||||
180.0: (
|
||||
+(RADIUS + PIN_OFFSET), -PIN_OFFSET,
|
||||
+(RADIUS + PIN_OFFSET), +PIN_OFFSET,
|
||||
-(RADIUS + PIN_OFFSET), -PIN_OFFSET,
|
||||
-(RADIUS + PIN_OFFSET), +PIN_OFFSET,
|
||||
),
|
||||
90.0: (
|
||||
+PIN_OFFSET, +(RADIUS - PIN_OFFSET),
|
||||
-PIN_OFFSET, +(RADIUS - PIN_OFFSET),
|
||||
+PIN_OFFSET, -(RADIUS - PIN_OFFSET),
|
||||
-PIN_OFFSET, -(RADIUS - PIN_OFFSET),
|
||||
),
|
||||
270.0: (
|
||||
-PIN_OFFSET, -(RADIUS - PIN_OFFSET),
|
||||
+PIN_OFFSET, -(RADIUS - PIN_OFFSET),
|
||||
-PIN_OFFSET, +(RADIUS - PIN_OFFSET),
|
||||
+PIN_OFFSET, +(RADIUS - PIN_OFFSET),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# KREISEL KLASSE
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class Kreisel(BaseModel):
|
||||
@@ -71,7 +121,6 @@ class Kreisel(BaseModel):
|
||||
@property
|
||||
def richtung_rad(self) -> float:
|
||||
"""Richtung in Radianten (für am_kreisel_direct_verbunden)."""
|
||||
# Wird aus drehung abgeleitet oder separat gesetzt
|
||||
return math.radians(self.drehung)
|
||||
|
||||
@property
|
||||
@@ -140,11 +189,22 @@ class Kreisel(BaseModel):
|
||||
anzahl_separatoren=anzahl_separatoren,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def draw_kreisel_lines(msp, pos1, pos2, kreisel):
|
||||
"""Zeichnet tangentiale Linien zwischen zwei Kreiselblöcken, unabhängig vom Winkel."""
|
||||
"""
|
||||
Zeichnet tangentiale Linien zwischen zwei Kreiselblöcken, unabhängig vom Winkel.
|
||||
|
||||
Für Pin-Kreisel werden zusätzlich Pinbereichs-Begrenzungslinien gezeichnet,
|
||||
die sich basierend auf der Drehung (0°/90°/180°/270°) verschieben.
|
||||
|
||||
Args:
|
||||
msp: DXF-Modelspace
|
||||
pos1, pos2: Positionen der beiden Kreisel-Blöcke (x, y, z)
|
||||
kreisel: Kreisel-Instanz
|
||||
"""
|
||||
rotation = kreisel.drehung
|
||||
x1, y1, z1 = pos1
|
||||
x2, y2, z1 = pos2
|
||||
x2, y2, _ = pos2 # z identisch für beide Blöcke
|
||||
# Verbindungsvektor
|
||||
dx = x2 - x1
|
||||
dy = y2 - y1
|
||||
@@ -153,7 +213,6 @@ class Kreisel(BaseModel):
|
||||
if length == 0:
|
||||
return # keine Linie bei identischen Punkten
|
||||
# Normalenvektor (senkrecht, normiert, Länge = RADIUS)
|
||||
|
||||
nx = -dy / length * RADIUS
|
||||
ny = dx / length * RADIUS
|
||||
# Tangentialpunkte
|
||||
@@ -161,72 +220,50 @@ class Kreisel(BaseModel):
|
||||
p1b = (x1 - nx, y1 - ny, z1)
|
||||
p2a = (x2 + nx, y2 + ny, z1)
|
||||
p2b = (x2 - nx, y2 - ny, z1)
|
||||
if kreisel.kreiselart == "Pin":
|
||||
if rotation == 0.0:
|
||||
p1a2 = p1a[0] - RADIUS - 50, p1a[1] + 50, z1
|
||||
p1b2 = p1b[0] - RADIUS - 50, p1b[1] - 50, z1
|
||||
p2a2 = p2a[0] + RADIUS + 50, p2a[1] + 50, z1
|
||||
p2b2 = p2b[0] + RADIUS + 50, p2b[1] - 50, z1
|
||||
Line1 = Line.new(
|
||||
dxfattribs={"start": p1a2, "end": p2a2, "layer": "Pinbereich"}
|
||||
)
|
||||
Line2 = Line.new(
|
||||
dxfattribs={"start": p1b2, "end": p2b2, "layer": "Pinbereich"}
|
||||
)
|
||||
msp.add_entity(Line1)
|
||||
msp.add_entity(Line2)
|
||||
elif rotation == 180.0:
|
||||
p1a2 = p1a[0] + RADIUS + 50, p1a[1] - 50, z1
|
||||
p1b2 = p1b[0] + RADIUS + 50, p1b[1] + 50, z1
|
||||
p2a2 = p2a[0] - RADIUS - 50, p2a[1] - 50, z1
|
||||
p2b2 = p2b[0] - RADIUS - 50, p2b[1] + 50, z1
|
||||
Line1 = Line.new(
|
||||
dxfattribs={"start": p1a2, "end": p2a2, "layer": "Pinbereich"}
|
||||
)
|
||||
Line2 = Line.new(
|
||||
dxfattribs={"start": p1b2, "end": p2b2, "layer": "Pinbereich"}
|
||||
)
|
||||
msp.add_entity(Line1)
|
||||
msp.add_entity(Line2)
|
||||
elif rotation == 90.0:
|
||||
p1a2 = p1a[0] + 50, p1a[1] - 50 + RADIUS, z1
|
||||
p1b2 = p1b[0] - 50, p1b[1] - 50 + RADIUS, z1
|
||||
p2a2 = p2a[0] + 50, p2a[1] + 50 - RADIUS, z1
|
||||
p2b2 = p2b[0] - 50, p2b[1] + 50 - RADIUS, z1
|
||||
Line1 = Line.new(
|
||||
dxfattribs={"start": p1a2, "end": p2a2, "layer": "Pinbereich"}
|
||||
)
|
||||
Line2 = Line.new(
|
||||
dxfattribs={"start": p1b2, "end": p2b2, "layer": "Pinbereich"}
|
||||
)
|
||||
msp.add_entity(Line1)
|
||||
msp.add_entity(Line2)
|
||||
elif rotation == 270.0:
|
||||
p1a2 = p1a[0] - 50, p1a[1] + 50 - RADIUS, z1
|
||||
p1b2 = p1b[0] + 50, p1b[1] + 50 - RADIUS, z1
|
||||
p2a2 = p2a[0] - 50, p2a[1] - 50 + RADIUS, z1
|
||||
p2b2 = p2b[0] + 50, p2b[1] - 50 + RADIUS, z1
|
||||
Line1 = Line.new(
|
||||
dxfattribs={"start": p1a2, "end": p2a2, "layer": "Pinbereich"}
|
||||
)
|
||||
Line2 = Line.new(
|
||||
dxfattribs={"start": p1b2, "end": p2b2, "layer": "Pinbereich"}
|
||||
)
|
||||
msp.add_entity(Line1)
|
||||
msp.add_entity(Line2)
|
||||
|
||||
# Linien zeichnen
|
||||
# Pin-Bereichsmarkierung (Lookup-Tabelle statt 4x copy-paste)
|
||||
if kreisel.kreiselart == "Pin" and rotation in PIN_OFFSETS:
|
||||
offsets = PIN_OFFSETS[rotation]
|
||||
points = [p1a, p1b, p2a, p2b]
|
||||
adjusted = [
|
||||
(p[0] + offsets[i * 2], p[1] + offsets[i * 2 + 1], z1)
|
||||
for i, p in enumerate(points)
|
||||
]
|
||||
p1a2, p1b2, p2a2, p2b2 = adjusted
|
||||
msp.add_entity(Line.new(
|
||||
dxfattribs={"start": p1a2, "end": p2a2, "layer": "Pinbereich"}
|
||||
))
|
||||
msp.add_entity(Line.new(
|
||||
dxfattribs={"start": p1b2, "end": p2b2, "layer": "Pinbereich"}
|
||||
))
|
||||
|
||||
# Hauptlinien zeichnen
|
||||
msp.add_line(p1a, p2a)
|
||||
msp.add_line(p1b, p2b)
|
||||
|
||||
@staticmethod
|
||||
def draw_kreisel_drehrichtung_markierung(
|
||||
msp, pos1, pos2, kreisel, lib_doc, doc, verbose
|
||||
):
|
||||
"""
|
||||
Zeichnet Richtungspfeile für die Drehrichtung eines Kreisels.
|
||||
|
||||
Platziert 3 Pfeile auf der oberen und 3 auf der unteren Tangentiallinie,
|
||||
mit invertierter Richtung zwischen oben und unten.
|
||||
|
||||
Args:
|
||||
msp: DXF-Modelspace
|
||||
pos1, pos2: Positionen der beiden Kreisel-Blöcke
|
||||
kreisel: Kreisel-Instanz
|
||||
lib_doc: Bibliotheks-Dokument
|
||||
doc: DXF-Dokument
|
||||
verbose: Ob Debug-Ausgabe aktiviert ist
|
||||
"""
|
||||
drehrichtung = (kreisel.drehrichtung or "").upper()
|
||||
if drehrichtung not in ("UZS", "GUZS"):
|
||||
return
|
||||
x1, y1, z1 = pos1
|
||||
x2, y2, z2 = pos2
|
||||
x2, y2, _ = pos2
|
||||
dx = x2 - x1
|
||||
dy = y2 - y1
|
||||
length = math.hypot(dx, dy)
|
||||
@@ -235,55 +272,59 @@ class Kreisel(BaseModel):
|
||||
# Normalenvektor (senkrecht, normiert, Länge = RADIUS)
|
||||
nx = -dy / length * RADIUS
|
||||
ny = dx / length * RADIUS
|
||||
# Obere Linie
|
||||
# Obere und untere Tangentiallinien
|
||||
p1_oben = (x1 + nx, y1 + ny)
|
||||
p2_oben = (x2 + nx, y2 + ny)
|
||||
# Untere Linie
|
||||
p1_unten = (x1 - nx, y1 - ny)
|
||||
p2_unten = (x2 - nx, y2 - ny)
|
||||
# S-LP auf oberer Linie (Drehrichtung wie angegeben)
|
||||
|
||||
# Richtungspfeil einmal importieren (nicht 6x in der Schleife)
|
||||
block_methoden.import_block("Richtungspfeil", lib_doc, doc)
|
||||
blockref_layer, color = block_methoden.get_insert_color_layer(lib_doc, "Richtungspfeil")
|
||||
|
||||
# Obere Linie: Drehrichtung wie angegeben
|
||||
Kreisel._place_richtungspfeile(
|
||||
msp, p1_oben, p2_oben, z1, drehrichtung,
|
||||
invert=False, blockref_layer=blockref_layer,
|
||||
verbose=verbose, label="oben",
|
||||
)
|
||||
# Untere Linie: Drehrichtung invertiert
|
||||
Kreisel._place_richtungspfeile(
|
||||
msp, p1_unten, p2_unten, z1, drehrichtung,
|
||||
invert=True, blockref_layer=blockref_layer,
|
||||
verbose=verbose, label="unten",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _place_richtungspfeile(msp, p1, p2, z, drehrichtung, invert, blockref_layer, verbose, label):
|
||||
"""
|
||||
Platziert 3 Richtungspfeile entlang einer Tangentiallinie.
|
||||
|
||||
Args:
|
||||
msp: DXF-Modelspace
|
||||
p1, p2: Start-/End-Punkt der Linie (x, y)
|
||||
z: Z-Koordinate
|
||||
drehrichtung: "UZS" oder "GUZS"
|
||||
invert: Ob die Richtung invertiert werden soll
|
||||
blockref_layer: Layer für den Block-Reference
|
||||
verbose: Ob Debug-Ausgabe aktiviert ist
|
||||
label: "oben" oder "unten" für Logging
|
||||
"""
|
||||
for i in range(1, 4):
|
||||
t = i / 4 # 1/4, 2/4, 3/4
|
||||
px = p1_oben[0] + t * (p2_oben[0] - p1_oben[0])
|
||||
py = p1_oben[1] + t * (p2_oben[1] - p1_oben[1])
|
||||
rotation = math.degrees(
|
||||
math.atan2(p2_oben[1] - p1_oben[1], p2_oben[0] - p1_oben[0])
|
||||
)
|
||||
if drehrichtung == "GUZS":
|
||||
px = p1[0] + t * (p2[0] - p1[0])
|
||||
py = p1[1] + t * (p2[1] - p1[1])
|
||||
rotation = math.degrees(math.atan2(p2[1] - p1[1], p2[0] - p1[0]))
|
||||
# Obere Linie: GUZS invertiert; Untere Linie: UZS invertiert
|
||||
should_invert = (drehrichtung == "GUZS" and not invert) or (drehrichtung == "UZS" and invert)
|
||||
if should_invert:
|
||||
rotation += 180
|
||||
block_methoden.import_block("Richtungspfeil", lib_doc, doc)
|
||||
blockref_layer, color = block_methoden.get_insert_color_layer(
|
||||
lib_doc, "Richtungspfeil"
|
||||
)
|
||||
bref = msp.add_blockref(
|
||||
msp.add_blockref(
|
||||
"Richtungspfeil",
|
||||
(px, py, z1),
|
||||
(px, py, z),
|
||||
dxfattribs={"rotation": rotation, "layer": blockref_layer},
|
||||
)
|
||||
if verbose:
|
||||
print(
|
||||
f"[INFO] Drehrichtung '{drehrichtung}': Richtungspfeil oben bei ({px:.1f}, {py:.1f}), rot={rotation:.1f}"
|
||||
)
|
||||
# S-LP auf unterer Linie (Drehrichtung invertiert)
|
||||
for i in range(1, 4):
|
||||
t = i / 4
|
||||
px = p1_unten[0] + t * (p2_unten[0] - p1_unten[0])
|
||||
py = p1_unten[1] + t * (p2_unten[1] - p1_unten[1])
|
||||
rotation = math.degrees(
|
||||
math.atan2(p2_unten[1] - p1_unten[1], p2_unten[0] - p1_unten[0])
|
||||
)
|
||||
if drehrichtung == "UZS":
|
||||
rotation += 180
|
||||
block_methoden.import_block("Richtungspfeil", lib_doc, doc)
|
||||
blockref_layer, color = block_methoden.get_insert_color_layer(
|
||||
lib_doc, "Richtungspfeil"
|
||||
)
|
||||
bref = msp.add_blockref(
|
||||
"Richtungspfeil",
|
||||
(px, py, z1),
|
||||
dxfattribs={"rotation": rotation, "layer": blockref_layer},
|
||||
)
|
||||
if verbose:
|
||||
print(
|
||||
f"[INFO] Drehrichtung '{drehrichtung}':Richtungspfeil unten bei ({px:.1f}, {py:.1f}), rot={rotation:.1f}"
|
||||
f"[INFO] Drehrichtung '{drehrichtung}': Richtungspfeil {label} bei ({px:.1f}, {py:.1f}), rot={rotation:.1f}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user