Linien und Permalink
Bernd Wurst

Bernd Wurst commited on 2026-06-21 10:55:28
Zeige 3 geänderte Dateien mit 128 Einfügungen und 57 Löschungen.

... ...
@@ -57,16 +57,16 @@
57 57
         </div>
58 58
     </div>
59 59
     <hr>
60
-    <h2>Punkte eintragen</h2>
60
+    <h2>Punkte und Linien eintragen</h2>
61 61
     <div class="control-group">
62
-        <label for="punkte-input">Punkte (z.B. A(2|4|3) oder B(-1|2|4)):</label>
62
+        <label for="punkte-input">Punkte (z.B. A(2|4|3) oder B(-1|2|4)) und Linien A-B oder A_B (gestrichelt):</label><br>
63 63
         <textarea id="punkte-input" rows="6" placeholder="A(2|4|3)&#10;B(-1|2|4)" py-input="zeichne_koordinatensystem">A(2|4|3)</textarea>
64 64
     </div>
65 65
 
66 66
     <div class="control-group checkbox-group">
67 67
         <label>
68 68
             <input type="checkbox" id="show-labels" checked py-input="zeichne_koordinatensystem">
69
-            Punkte überhaupt beschriften
69
+            Punkte beschriften
70 70
         </label>
71 71
     </div>
72 72
     <div class="control-group checkbox-group">
... ...
@@ -1,61 +1,126 @@
1 1
 import re
2
+import json
3
+import base64
2 4
 from pyscript import document, window
3 5
 from schraegbild.svg_builder import SchraegbildBuilder
4 6
 
5
-# Globale Variable, um den aktuellen SVG-String für den Download zwischenzuspeichern
7
+# Globale Variable für den Download
6 8
 aktuelles_svg = ""
9
+# Flag, um Endlosschleifen beim Laden des Hashs zu verhindern
10
+block_hash_update = False
11
+
12
+
13
+def get_int_value(selector, default=0):
14
+    val_string = document.querySelector(selector).value.strip()
15
+    if not val_string: return default
16
+    try:
17
+        return int(val_string)
18
+    except ValueError:
19
+        return default
7 20
 
8 21
 
9 22
 def zeichne_koordinatensystem(event=None):
10
-    global aktuelles_svg
23
+    global aktuelles_svg, block_hash_update
11 24
 
12
-    # 1. Werte auslesen wie bisher
13
-    len_x1 = int(document.querySelector("#len-x1").value)
14
-    len_x2 = int(document.querySelector("#len-x2").value)
15
-    len_x3 = int(document.querySelector("#len-x3").value)
16
-    neg_x1 = int(document.querySelector("#neg-x1").value)
17
-    neg_x2 = int(document.querySelector("#neg-x2").value)
18
-    neg_x3 = int(document.querySelector("#neg-x3").value)
19
-    mod_x1 = int(document.querySelector("#mod-x1").value)
20
-    mod_x2 = int(document.querySelector("#mod-x2").value)
21
-    mod_x3 = int(document.querySelector("#mod-x3").value)
25
+    # 1. Werte auslesen
26
+    len_x1 = get_int_value("#len-x1", 5)
27
+    len_x2 = get_int_value("#len-x2", 6)
28
+    len_x3 = get_int_value("#len-x3", 6)
29
+    neg_x1 = get_int_value("#neg-x1", 0)
30
+    neg_x2 = get_int_value("#neg-x2", 0)
31
+    neg_x3 = get_int_value("#neg-x3", 0)
32
+    mod_x1 = get_int_value("#mod-x1", 2)
33
+    mod_x2 = get_int_value("#mod-x2", 2)
34
+    mod_x3 = get_int_value("#mod-x3", 2)
22 35
 
23 36
     show_labels = document.querySelector("#show-labels").checked
24 37
     show_coords = document.querySelector("#show-coords").checked
25 38
     punkte_text = document.querySelector("#punkte-input").value
26 39
 
27
-    # 2. Punkte parsen
40
+    # --- NEU: Zustand in der URL speichern ---
41
+    if not block_hash_update:
42
+        zustand = {
43
+            "x1": len_x1, "x2": len_x2, "x3": len_x3,
44
+            "nx1": neg_x1, "nx2": neg_x2, "nx3": neg_x3,
45
+            "mx1": mod_x1, "mx2": mod_x2, "mx3": mod_x3,
46
+            "lbl": show_labels, "crd": show_coords,
47
+            "txt": punkte_text
48
+        }
49
+        # Als JSON packen, in Bytes umwandeln, Base64 codieren und als UTF-8 String in die URL schieben
50
+        json_bytes = json.dumps(zustand).encode('utf-8')
51
+        b64_string = base64.b64encode(json_bytes).decode('utf-8')
52
+        window.location.hash = b64_string
53
+
54
+    # 2. Punkte & Kanten parsen
28 55
     punkte_liste = []
29
-    pattern = re.compile(r"([A-Za-z0-9_]+)\s*\(\s*([-\d.]+)\s*\|\s*([-\d.]+)\s*\|\s*([-\d.]+)\s*\)")
56
+    kanten_liste = []
57
+    p_pattern = re.compile(r"([A-Za-z0-9_]+)\s*\(\s*([-\d.]+)\s*\|\s*([-\d.]+)\s*\|\s*([-\d.]+)\s*\)")
58
+    k_pattern = re.compile(r"([A-Za-z0-9_]+)\s*([-_])\s*([A-Za-z0-9_]+)")
30 59
 
31 60
     for zeile in punkte_text.splitlines():
32
-        match = pattern.search(zeile)
33
-        if match:
34
-            name = match.group(1)
35
-            x1 = float(match.group(2))
36
-            x2 = float(match.group(3))
37
-            x3 = float(match.group(4))
38
-            punkte_liste.append({"name": name, "coords": (x1, x2, x3)})
39
-
40
-    # 3. Builder aufrufen
61
+        zeile = zeile.strip()
62
+        if not zeile: continue
63
+        p_match = p_pattern.search(zeile)
64
+        if p_match:
65
+            punkte_liste.append({"name": p_match.group(1),
66
+                                 "coords": (float(p_match.group(2)), float(p_match.group(3)), float(p_match.group(4)))})
67
+            continue
68
+        k_match = k_pattern.search(zeile)
69
+        if k_match:
70
+            kanten_liste.append({"von": k_match.group(1), "nach": k_match.group(3),
71
+                                 "style": "sichtbar" if k_match.group(2) == "-" else "verdeckt"})
72
+
73
+    # 3. Rendern
41 74
     builder = SchraegbildBuilder(
42
-        len_x1, len_x2, len_x3, neg_x1, neg_x2, neg_x3, mod_x1, mod_x2, mod_x3,
43
-        punkte=punkte_liste, show_labels=show_labels, show_coords=show_coords
75
+        len_x1, len_x2, len_x3, neg_x1, neg_x2, neg_x3, mod_x1, max(1, mod_x2), max(1, mod_x3),
76
+        punkte=punkte_liste, kanten=kanten_liste, show_labels=show_labels, show_coords=show_coords
44 77
     )
45 78
 
46
-    # SVG generieren und global speichern
47 79
     aktuelles_svg = builder.generiere_koordinatensystem()
48
-
49
-    # Im UI anzeigen
50 80
     document.querySelector("#canvas-container").innerHTML = aktuelles_svg
51 81
 
52 82
 
83
+def lade_zustand_aus_url():
84
+    """Prüft beim Start, ob ein Permalink geladen wurde"""
85
+    global block_hash_update
86
+    hash_val = window.location.hash
87
+
88
+    if not hash_val or len(hash_val) <= 1:
89
+        return  # Kein Permalink vorhanden, Standardwerte nutzen
90
+
91
+    try:
92
+        # Das '#' vorne abschneiden und Base64 decodieren
93
+        b64_data = hash_val[1:]
94
+        json_bytes = base64.b64decode(b64_data)
95
+        zustand = json.loads(json_bytes.decode('utf-8'))
96
+
97
+        # UI-Updates blockieren den Hash-Writer temporär, um Echos zu verhindern
98
+        block_hash_update = True
99
+
100
+        # Werte in die HTML-Felder zurückschreiben
101
+        document.querySelector("#len-x1").value = zustand.get("x1", 5)
102
+        document.querySelector("#len-x2").value = zustand.get("x2", 6)
103
+        document.querySelector("#len-x3").value = zustand.get("x3", 6)
104
+        document.querySelector("#neg-x1").value = zustand.get("nx1", 0)
105
+        document.querySelector("#neg-x2").value = zustand.get("nx2", 0)
106
+        document.querySelector("#neg-x3").value = zustand.get("nx3", 0)
107
+        document.querySelector("#mod-x1").value = zustand.get("mx1", 2)
108
+        document.querySelector("#mod-x2").value = zustand.get("mx2", 2)
109
+        document.querySelector("#mod-x3").value = zustand.get("mx3", 2)
110
+
111
+        document.querySelector("#show-labels").checked = zustand.get("lbl", True)
112
+        document.querySelector("#show-coords").checked = zustand.get("crd", True)
113
+        document.querySelector("#punkte-input").value = zustand.get("txt", "")
114
+
115
+        block_hash_update = False
116
+    except Exception as e:
117
+        print(f"Fehler beim Laden des Permalinks: {e}")
118
+        block_hash_update = False
119
+
120
+
53 121
 def download_svg(event=None):
54 122
     global aktuelles_svg
55
-    if not aktuelles_svg:
56
-        return
57
-
58
-    # Über das JavaScript-Window-Objekt einen unsichtbaren Download-Link bauen
123
+    if not aktuelles_svg: return
59 124
     js_code = f"""
60 125
     var blob = new Blob([{repr(aktuelles_svg)}], {{type: "image/svg+xml;charset=utf-8"}});
61 126
     var url = URL.createObjectURL(blob);
... ...
@@ -67,9 +132,11 @@ def download_svg(event=None):
67 132
     document.body.removeChild(a);
68 133
     URL.revokeObjectURL(url);
69 134
     """
70
-    # Den JS-Code im Browser-Kontext ausführen
71 135
     window.eval(js_code)
72 136
 
73 137
 
74
-# Initialer Aufruf
138
+# --- START-SEQUENZ ---
139
+# 1. Prüfen, ob Voreinstellungen aus einem Link geladen werden müssen
140
+lade_zustand_aus_url()
141
+# 2. Erstmalig zeichnen
75 142
 zeichne_koordinatensystem()
76 143
\ No newline at end of file
... ...
@@ -6,7 +6,7 @@ from schraegbild.projektion import projiziere_3d_auf_2d
6 6
 class SchraegbildBuilder:
7 7
     def __init__(self, len_x1: int, len_x2: int, len_x3: int, neg_x1: int, neg_x2: int, neg_x3: int,
8 8
                  mod_x1: int = 2, mod_x2: int = 2, mod_x3: int = 2, skalierung: float = 40.0,
9
-                 punkte: list = None, show_labels: bool = True, show_coords: bool = True):
9
+                 punkte: list = None, kanten: list = None, show_labels: bool = True, show_coords: bool = True):
10 10
 
11 11
         self.len_x1 = len_x1
12 12
         self.len_x2 = len_x2
... ...
@@ -19,17 +19,17 @@ class SchraegbildBuilder:
19 19
         self.mod_x3 = mod_x3
20 20
         self.skalierung = skalierung
21 21
 
22
-        # NEU: Parameter für Punkte sichern
23 22
         self.punkte = punkte if punkte is not None else []
23
+        self.kanten = kanten if kanten is not None else []
24 24
         self.show_labels = show_labels
25 25
         self.show_coords = show_coords
26 26
 
27
+        # Ein schnelles Dictionary bauen, um Koordinaten via Name sofort zu finden
28
+        self.punkt_dict = {p["name"]: p["coords"] for p in self.punkte}
29
+
30
+        # [Rest der Viewport-Berechnung bleibt unverändert wie zuvor]
27 31
         kaestchen = skalierung / 2.0
28 32
         padding = 2 * skalierung
29
-
30
-        # --- Extrempunkte berechnen (Jetzt inkl. der eingetragenen Punkte!) ---
31
-        # Damit das Skript nicht abstürzt, wenn ein Lehrer einen Punkt außerhalb des Systems einträgt,
32
-        # beziehen wir die Punkte dynamisch in den Viewport mit ein:
33 33
         punkte_fuer_viewport = [
34 34
             projiziere_3d_auf_2d(self.len_x1 + 1, 0, 0, skalierung),
35 35
             projiziere_3d_auf_2d(-self.neg_x1, 0, 0, skalierung),
... ...
@@ -75,9 +75,8 @@ class SchraegbildBuilder:
75 75
 
76 76
     def generiere_koordinatensystem(self) -> str:
77 77
         dwg = self.dwg
78
-        # [Hier bleibt der komplette bisherige Code für Hintergrund, Achsen, Ticks, Beschriftungen unverändert]
78
+        # [Hier Achsen, Hintergrund & Ticks wie gewohnt zeichnen lassen...]
79 79
         dwg.add(dwg.rect(insert=(0, 0), size=(self.breite, self.hoehe), fill=self.gitter_pattern.get_paint_server()))
80
-
81 80
         p_ursprung = self.to_svg(0, 0, 0)
82 81
         dwg.add(dwg.line(p_ursprung, self.to_svg(self.len_x1 + 1, 0, 0), stroke='black', stroke_width=1.5,
83 82
                          marker_end=self.pfeil.get_funciri()))
... ...
@@ -136,29 +135,34 @@ class SchraegbildBuilder:
136 135
         dwg.add(dwg.text("x₃", insert=(p_x3_label[0] - 5, p_x3_label[1] - 15),
137 136
                          style="font-family:Arial; font-size:14px; font-weight:bold;"))
138 137
 
139
-        # --- AB HIER NEU: PUNKTE ZEICHNEN ---
138
+        # --- NEU: ERST DIE KANTEN ZEICHNEN ---
139
+        for k in self.kanten:
140
+            if k["von"] in self.punkt_dict and k["nach"] in self.punkt_dict:
141
+                v_x, v_y = self.to_svg(*self.punkt_dict[k["von"]])
142
+                n_x, n_y = self.to_svg(*self.punkt_dict[k["nach"]])
143
+
144
+                if k["style"] == "sichtbar":
145
+                    # Durchgezogene, blaue Schulbuchkante
146
+                    dwg.add(dwg.line((v_x, v_y), (n_x, n_y), stroke='#0055ff', stroke_width=2))
147
+                else:
148
+                    # Gestrichelte Kante für verdeckte Geometrien
149
+                    dwg.add(
150
+                        dwg.line((v_x, v_y), (n_x, n_y), stroke='#0055ff', stroke_width=1.5, stroke_dasharray='4,4'))
151
+
152
+        # --- DANN DIE PUNKTE DARÜBER LEGEN ---
140 153
         for p in self.punkte:
141 154
             x1, x2, x3 = p["coords"]
142 155
             pt = self.to_svg(x1, x2, x3)
143
-
144
-            # Punktkreuz zeichnen (Größe: 4px Armlänge, rot hervorgehoben für Fokus)
145 156
             dwg.add(dwg.line((pt[0] - 4, pt[1] - 4), (pt[0] + 4, pt[1] + 4), stroke='red', stroke_width=1.5))
146 157
             dwg.add(dwg.line((pt[0] - 4, pt[1] + 4), (pt[0] + 4, pt[1] - 4), stroke='red', stroke_width=1.5))
147 158
 
148
-            # Beschriftungslogik auswerten
149 159
             if self.show_labels:
150 160
                 if self.show_coords:
151
-                    # Format: A(2|4|3) - wir formatieren eventuelle Floats sauber weg, wenn es Ganzzahlen sind
152 161
                     fmt_coords = f"({int(x1) if x1.is_integer() else x1}|{int(x2) if x2.is_integer() else x2}|{int(x3) if x3.is_integer() else x3})"
153 162
                     label_text = f"{p['name']}{fmt_coords}"
154 163
                 else:
155
-                    # Nur Name: A
156 164
                     label_text = p["name"]
157
-
158
-                dwg.add(dwg.text(
159
-                    label_text,
160
-                    insert=(pt[0] + 6, pt[1] - 6),
161
-                    style="font-family:Arial, sans-serif; font-size:13px; font-weight:bold; fill:red;"
162
-                ))
165
+                dwg.add(dwg.text(label_text, insert=(pt[0] + 6, pt[1] - 6),
166
+                                 style="font-family:Arial, sans-serif; font-size:13px; font-weight:bold; fill:red;"))
163 167
 
164 168
         return dwg.tostring()
165 169
\ No newline at end of file
166 170