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) 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 |