initial commit
Bernd Wurst

Bernd Wurst commited on 2026-06-21 10:36:00
Zeige 12 geänderte Dateien mit 426 Einfügungen und 0 Löschungen.

... ...
@@ -0,0 +1,5 @@
1
+# Default ignored files
2
+/shelf/
3
+/workspace.xml
4
+# Editor-based HTTP Client requests
5
+/httpRequests/
... ...
@@ -0,0 +1,12 @@
1
+<component name="InspectionProjectProfileManager">
2
+  <profile version="1.0">
3
+    <option name="myName" value="Project Default" />
4
+    <inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
5
+      <option name="ignoredIdentifiers">
6
+        <list>
7
+          <option value="lib.DSFinVK.dsfinvk" />
8
+        </list>
9
+      </option>
10
+    </inspection_tool>
11
+  </profile>
12
+</component>
0 13
\ No newline at end of file
... ...
@@ -0,0 +1,6 @@
1
+<component name="InspectionProjectProfileManager">
2
+  <settings>
3
+    <option name="USE_PROJECT_PROFILE" value="false" />
4
+    <version value="1.0" />
5
+  </settings>
6
+</component>
0 7
\ No newline at end of file
... ...
@@ -0,0 +1,8 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project version="4">
3
+  <component name="ProjectModuleManager">
4
+    <modules>
5
+      <module fileurl="file://$PROJECT_DIR$/.idea/schraegbild.iml" filepath="$PROJECT_DIR$/.idea/schraegbild.iml" />
6
+    </modules>
7
+  </component>
8
+</project>
0 9
\ No newline at end of file
... ...
@@ -0,0 +1,10 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<module type="PYTHON_MODULE" version="4">
3
+  <component name="NewModuleRootManager">
4
+    <content url="file://$MODULE_DIR$">
5
+      <excludeFolder url="file://$MODULE_DIR$/.venv" />
6
+    </content>
7
+    <orderEntry type="jdk" jdkName="Python 3.12 (schraegbild)" jdkType="Python SDK" />
8
+    <orderEntry type="sourceFolder" forTests="false" />
9
+  </component>
10
+</module>
0 11
\ No newline at end of file
... ...
@@ -0,0 +1,6 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project version="4">
3
+  <component name="VcsDirectoryMappings">
4
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+  </component>
6
+</project>
0 7
\ No newline at end of file
... ...
@@ -0,0 +1,98 @@
1
+<!DOCTYPE html>
2
+<html lang="de">
3
+<head>
4
+    <meta charset="UTF-8">
5
+    <title>Schrägbild-Generator</title>
6
+    <link rel="stylesheet" href="styles.css">
7
+    <link rel="stylesheet" href="https://pyscript.net/releases/2026.6.1/core.css" />
8
+    <script type="module" src="https://pyscript.net/releases/2026.6.1/core.js"></script>
9
+</head>
10
+<body>
11
+
12
+<div class="sidebar">
13
+    <h2>Koordinatensystem</h2>
14
+
15
+    <div class="control-group row">
16
+        <div>
17
+            <label for="len-x1">Max x₁ (+):</label>
18
+            <input type="number" id="len-x1" min="0" max="30" value="5" py-input="zeichne_koordinatensystem">
19
+        </div>
20
+        <div>
21
+            <label for="neg-x1">Max x₁ (-):</label>
22
+            <input type="number" id="neg-x1" min="0" max="30" value="2" py-input="zeichne_koordinatensystem">
23
+        </div>
24
+        <div>
25
+            <label for="mod-x1">Schrittw.:</label>
26
+            <input type="number" id="mod-x1" min="1" max="10" value="2" py-input="zeichne_koordinatensystem">
27
+        </div>
28
+    </div>
29
+
30
+    <div class="control-group row">
31
+        <div>
32
+            <label for="len-x2">Max x₂ (+):</label>
33
+            <input type="number" id="len-x2" min="0" max="30" value="6" py-input="zeichne_koordinatensystem">
34
+        </div>
35
+        <div>
36
+            <label for="neg-x2">Max x₂ (-):</label>
37
+            <input type="number" id="neg-x2" min="0" max="30" value="2" py-input="zeichne_koordinatensystem">
38
+        </div>
39
+        <div>
40
+            <label for="mod-x2">Schrittw.:</label>
41
+            <input type="number" id="mod-x2" min="1" max="10" value="2" py-input="zeichne_koordinatensystem">
42
+        </div>
43
+    </div>
44
+
45
+    <div class="control-group row">
46
+        <div>
47
+            <label for="len-x3">Max x₃ (+):</label>
48
+            <input type="number" id="len-x3" min="0" max="30" value="6" py-input="zeichne_koordinatensystem">
49
+        </div>
50
+        <div>
51
+            <label for="neg-x3">Max x₃ (-):</label>
52
+            <input type="number" id="neg-x3" min="0" max="30" value="2" py-input="zeichne_koordinatensystem">
53
+        </div>
54
+        <div>
55
+            <label for="mod-x3">Schrittw.:</label>
56
+            <input type="number" id="mod-x3" min="1" max="10" value="2" py-input="zeichne_koordinatensystem">
57
+        </div>
58
+    </div>
59
+    <hr>
60
+    <h2>Punkte eintragen</h2>
61
+    <div class="control-group">
62
+        <label for="punkte-input">Punkte (z.B. A(2|4|3) oder B(-1|2|4)):</label>
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
+    </div>
65
+
66
+    <div class="control-group checkbox-group">
67
+        <label>
68
+            <input type="checkbox" id="show-labels" checked py-input="zeichne_koordinatensystem">
69
+            Punkte überhaupt beschriften
70
+        </label>
71
+    </div>
72
+    <div class="control-group checkbox-group">
73
+        <label>
74
+            <input type="checkbox" id="show-coords" checked py-input="zeichne_koordinatensystem">
75
+            Koordinaten mit anzeigen (z.B. A(2|4|3))
76
+        </label>
77
+    </div>
78
+    <hr>
79
+    <button id="btn-download" class="btn-primary" py-click="download_svg">
80
+        SVG herunterladen
81
+    </button>
82
+
83
+</div>
84
+
85
+    <div class="main-content">
86
+        <div id="canvas-container"></div>
87
+    </div>
88
+
89
+    <script type="py" src="./main.py" config='{
90
+        "packages": ["svgwrite"],
91
+        "files": {
92
+            "./schraegbild/__init__.py": "schraegbild/__init__.py",
93
+            "./schraegbild/projektion.py": "schraegbild/projektion.py",
94
+            "./schraegbild/svg_builder.py": "schraegbild/svg_builder.py"
95
+        }
96
+    }'></script>
97
+</body>
98
+</html>
0 99
\ No newline at end of file
... ...
@@ -0,0 +1,75 @@
1
+import re
2
+from pyscript import document, window
3
+from schraegbild.svg_builder import SchraegbildBuilder
4
+
5
+# Globale Variable, um den aktuellen SVG-String für den Download zwischenzuspeichern
6
+aktuelles_svg = ""
7
+
8
+
9
+def zeichne_koordinatensystem(event=None):
10
+    global aktuelles_svg
11
+
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)
22
+
23
+    show_labels = document.querySelector("#show-labels").checked
24
+    show_coords = document.querySelector("#show-coords").checked
25
+    punkte_text = document.querySelector("#punkte-input").value
26
+
27
+    # 2. Punkte parsen
28
+    punkte_liste = []
29
+    pattern = re.compile(r"([A-Za-z0-9_]+)\s*\(\s*([-\d.]+)\s*\|\s*([-\d.]+)\s*\|\s*([-\d.]+)\s*\)")
30
+
31
+    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
41
+    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
44
+    )
45
+
46
+    # SVG generieren und global speichern
47
+    aktuelles_svg = builder.generiere_koordinatensystem()
48
+
49
+    # Im UI anzeigen
50
+    document.querySelector("#canvas-container").innerHTML = aktuelles_svg
51
+
52
+
53
+def download_svg(event=None):
54
+    global aktuelles_svg
55
+    if not aktuelles_svg:
56
+        return
57
+
58
+    # Über das JavaScript-Window-Objekt einen unsichtbaren Download-Link bauen
59
+    js_code = f"""
60
+    var blob = new Blob([{repr(aktuelles_svg)}], {{type: "image/svg+xml;charset=utf-8"}});
61
+    var url = URL.createObjectURL(blob);
62
+    var a = document.createElement("a");
63
+    a.href = url;
64
+    a.download = "schraegbild.svg";
65
+    document.body.appendChild(a);
66
+    a.click();
67
+    document.body.removeChild(a);
68
+    URL.revokeObjectURL(url);
69
+    """
70
+    # Den JS-Code im Browser-Kontext ausführen
71
+    window.eval(js_code)
72
+
73
+
74
+# Initialer Aufruf
75
+zeichne_koordinatensystem()
0 76
\ No newline at end of file
... ...
@@ -0,0 +1 @@
1
+pass
0 2
\ No newline at end of file
... ...
@@ -0,0 +1,41 @@
1
+import math
2
+
3
+# Konstanten für die Schulbuch-Projektion
4
+WINKEL_X1_GRAD = 135  # 135 Grad im mathematisch positiven Drehsinn (nach unten-links)
5
+VERKUERZUNG_X1 = 1.0 / math.sqrt(2)  # ca. 0.7071
6
+
7
+
8
+def projiziere_3d_auf_2d(
9
+        x1: float,
10
+        x2: float,
11
+        x3: float,
12
+        skalierung: float = 40.0
13
+) -> tuple[float, float]:
14
+    """
15
+    Rechnet eine 3D-Koordinate (x1, x2, x3) in eine relative 2D-Ebene um.
16
+
17
+    :param x1: Koordinate nach vorne-links
18
+    :param x2: Koordinate nach rechts
19
+    :param x3: Koordinate nach oben
20
+    :param skalierung: Multiplikator, wie viele Pixel eine Längeneinheit (LE) groß ist (z.B. 40px = 1cm).
21
+    :return: (x_relativ, y_relativ) bezogen auf den mathematischen Ursprung (0,0).
22
+    """
23
+    # Winkel in Bogenmaß umrechnen
24
+    # Da wir nach unten-links wollen, nutzen wir den Winkel direkt.
25
+    winkel_rad = math.radians(WINKEL_X1_GRAD)
26
+
27
+    # 1. Berechnung des x-Werts auf dem Papier (relativ zum Ursprung):
28
+    # x2 geht direkt nach rechts (+).
29
+    # x1 geht nach links (-), das ist im cos(135°) bereits mathematisch korrekt enthalten.
30
+    x_2d = x2 + (x1 * VERKUERZUNG_X1 * math.cos(winkel_rad))
31
+
32
+    # 2. Berechnung des y-Werts auf dem Papier (relativ zum Ursprung):
33
+    # x3 geht nach oben (+).
34
+    # x1 geht nach unten (-), das ist im sin(135°) positiv, aber da im SVG 'unten' positiv ist,
35
+    # müssen wir die mathematische Richtung umkehren.
36
+    # Mathematisch: y = x3 + x1 * verkürzung * sin(135°)
37
+    # Für SVG (da y nach unten wächst): Wir negieren den gesamten mathematischen y-Wert.
38
+    y_2d = (x1 * VERKUERZUNG_X1 * math.sin(winkel_rad)) - x3
39
+
40
+    # Mit dem Skalierungsfaktor (Pixel pro Längeneinheit) multiplizieren
41
+    return x_2d * skalierung, y_2d * skalierung
0 42
\ No newline at end of file
... ...
@@ -0,0 +1,164 @@
1
+import math
2
+import svgwrite
3
+from schraegbild.projektion import projiziere_3d_auf_2d
4
+
5
+
6
+class SchraegbildBuilder:
7
+    def __init__(self, len_x1: int, len_x2: int, len_x3: int, neg_x1: int, neg_x2: int, neg_x3: int,
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):
10
+
11
+        self.len_x1 = len_x1
12
+        self.len_x2 = len_x2
13
+        self.len_x3 = len_x3
14
+        self.neg_x1 = neg_x1
15
+        self.neg_x2 = neg_x2
16
+        self.neg_x3 = neg_x3
17
+        self.mod_x1 = mod_x1
18
+        self.mod_x2 = mod_x2
19
+        self.mod_x3 = mod_x3
20
+        self.skalierung = skalierung
21
+
22
+        # NEU: Parameter für Punkte sichern
23
+        self.punkte = punkte if punkte is not None else []
24
+        self.show_labels = show_labels
25
+        self.show_coords = show_coords
26
+
27
+        kaestchen = skalierung / 2.0
28
+        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
+        punkte_fuer_viewport = [
34
+            projiziere_3d_auf_2d(self.len_x1 + 1, 0, 0, skalierung),
35
+            projiziere_3d_auf_2d(-self.neg_x1, 0, 0, skalierung),
36
+            projiziere_3d_auf_2d(0, self.len_x2 + 1, 0, skalierung),
37
+            projiziere_3d_auf_2d(0, -self.neg_x2, 0, skalierung),
38
+            projiziere_3d_auf_2d(0, 0, self.len_x3 + 1, skalierung),
39
+            projiziere_3d_auf_2d(0, 0, -self.neg_x3, skalierung)
40
+        ]
41
+        for p in self.punkte:
42
+            p_x1, p_x2, p_x3 = p["coords"]
43
+            punkte_fuer_viewport.append(projiziere_3d_auf_2d(p_x1, p_x2, p_x3, skalierung))
44
+
45
+        min_x = min(p[0] for p in punkte_fuer_viewport) - padding
46
+        max_x = max(p[0] for p in punkte_fuer_viewport) + padding
47
+        min_y = min(p[1] for p in punkte_fuer_viewport) - padding
48
+        max_y = max(p[1] for p in punkte_fuer_viewport) + padding
49
+
50
+        min_x_grid = math.floor(min_x / kaestchen) * kaestchen
51
+        min_y_grid = math.floor(min_y / kaestchen) * kaestchen
52
+        max_x_grid = math.ceil(max_x / kaestchen) * kaestchen
53
+        max_y_grid = math.ceil(max_y / kaestchen) * kaestchen
54
+
55
+        self.breite = int(max_x_grid - min_x_grid)
56
+        self.hoehe = int(max_y_grid - min_y_grid)
57
+        self.ursprung_x = int(-min_x_grid)
58
+        self.ursprung_y = int(-min_y_grid)
59
+
60
+        self.dwg = svgwrite.Drawing(size=(f"{self.breite}px", f"{self.hoehe}px"), profile='full')
61
+        self.dwg.viewbox(0, 0, self.breite, self.hoehe)
62
+
63
+        self.pfeil = self.dwg.marker(insert=(9, 4), size=(10, 8), orient='auto')
64
+        self.pfeil.add(self.dwg.path(d="M 0,0 L 10,4 L 0,8 Z", fill='black'))
65
+        self.dwg.defs.add(self.pfeil)
66
+
67
+        self.gitter_pattern = self.dwg.pattern(size=(f"{kaestchen}px", f"{kaestchen}px"), patternUnits='userSpaceOnUse')
68
+        self.gitter_pattern.add(
69
+            self.dwg.path(d=f"M {kaestchen},0 L 0,0 0,{kaestchen}", fill='none', stroke='#e5e5e5', stroke_width=0.5))
70
+        self.dwg.defs.add(self.gitter_pattern)
71
+
72
+    def to_svg(self, x1, x2, x3):
73
+        rel_x, rel_y = projiziere_3d_auf_2d(x1, x2, x3, self.skalierung)
74
+        return (self.ursprung_x + rel_x, self.ursprung_y + rel_y)
75
+
76
+    def generiere_koordinatensystem(self) -> str:
77
+        dwg = self.dwg
78
+        # [Hier bleibt der komplette bisherige Code für Hintergrund, Achsen, Ticks, Beschriftungen unverändert]
79
+        dwg.add(dwg.rect(insert=(0, 0), size=(self.breite, self.hoehe), fill=self.gitter_pattern.get_paint_server()))
80
+
81
+        p_ursprung = self.to_svg(0, 0, 0)
82
+        dwg.add(dwg.line(p_ursprung, self.to_svg(self.len_x1 + 1, 0, 0), stroke='black', stroke_width=1.5,
83
+                         marker_end=self.pfeil.get_funciri()))
84
+        dwg.add(dwg.line(p_ursprung, self.to_svg(0, self.len_x2 + 1, 0), stroke='black', stroke_width=1.5,
85
+                         marker_end=self.pfeil.get_funciri()))
86
+        dwg.add(dwg.line(p_ursprung, self.to_svg(0, 0, self.len_x3 + 1), stroke='black', stroke_width=1.5,
87
+                         marker_end=self.pfeil.get_funciri()))
88
+
89
+        style_neg = {"stroke": "#777777", "stroke_width": 1.2, "stroke_dasharray": "4,4"}
90
+        if self.neg_x1 > 0: dwg.add(dwg.line(p_ursprung, self.to_svg(-self.neg_x1-1, 0, 0), **style_neg))
91
+        if self.neg_x2 > 0: dwg.add(dwg.line(p_ursprung, self.to_svg(0, -self.neg_x2-1, 0), **style_neg))
92
+        if self.neg_x3 > 0: dwg.add(dwg.line(p_ursprung, self.to_svg(0, 0, -self.neg_x3-1), **style_neg))
93
+
94
+        text_style = "font-family:Arial, sans-serif; font-size:12px;"
95
+        text_style_neg = "font-family:Arial, sans-serif; font-size:12px; fill: #666666;"
96
+
97
+        for i in range(-self.neg_x1, self.len_x1 + 1):
98
+            if i == 0: continue
99
+            p_tick = self.to_svg(i, 0, 0)
100
+            dwg.add(dwg.line((p_tick[0] - 3, p_tick[1] - 3), (p_tick[0] + 3, p_tick[1] + 3),
101
+                             stroke='black' if i > 0 else '#777777', stroke_width=1))
102
+            if i % self.mod_x1 == 0:
103
+                offset_x = p_tick[0] - 18 if i < 0 else p_tick[0] - 16
104
+                dwg.add(dwg.text(str(i), insert=(offset_x, p_tick[1] - 6 if i > 0 else p_tick[1] + 8),
105
+                                 style=text_style if i > 0 else text_style_neg))
106
+
107
+        for i in range(-self.neg_x2, self.len_x2 + 1):
108
+            if i == 0: continue
109
+            p_tick = self.to_svg(0, i, 0)
110
+            dwg.add(
111
+                dwg.line((p_tick[0], p_tick[1] - 3), (p_tick[0], p_tick[1] + 3), stroke='black' if i > 0 else '#777777',
112
+                         stroke_width=1))
113
+            if i % self.mod_x2 == 0:
114
+                offset_x = p_tick[0] - 5 if i < 0 else p_tick[0] - 3
115
+                dwg.add(
116
+                    dwg.text(str(i), insert=(offset_x, p_tick[1] + 16), style=text_style if i > 0 else text_style_neg))
117
+
118
+        for i in range(-self.neg_x3, self.len_x3 + 1):
119
+            if i == 0: continue
120
+            p_tick = self.to_svg(0, 0, i)
121
+            dwg.add(
122
+                dwg.line((p_tick[0] - 3, p_tick[1]), (p_tick[0] + 3, p_tick[1]), stroke='black' if i > 0 else '#777777',
123
+                         stroke_width=1))
124
+            if i % self.mod_x3 == 0:
125
+                offset_x = p_tick[0] - 22 if i < 0 else p_tick[0] - 16
126
+                dwg.add(
127
+                    dwg.text(str(i), insert=(offset_x, p_tick[1] + 4), style=text_style if i > 0 else text_style_neg))
128
+
129
+        p_x1_label = self.to_svg(self.len_x1 + 1, 0, 0)
130
+        p_x2_label = self.to_svg(0, self.len_x2 + 1, 0)
131
+        p_x3_label = self.to_svg(0, 0, self.len_x3 + 1)
132
+        dwg.add(dwg.text("x₁", insert=(p_x1_label[0] - 15, p_x1_label[1] + 20),
133
+                         style="font-family:Arial; font-size:14px; font-weight:bold;"))
134
+        dwg.add(dwg.text("x₂", insert=(p_x2_label[0] + 12, p_x2_label[1] + 5),
135
+                         style="font-family:Arial; font-size:14px; font-weight:bold;"))
136
+        dwg.add(dwg.text("x₃", insert=(p_x3_label[0] - 5, p_x3_label[1] - 15),
137
+                         style="font-family:Arial; font-size:14px; font-weight:bold;"))
138
+
139
+        # --- AB HIER NEU: PUNKTE ZEICHNEN ---
140
+        for p in self.punkte:
141
+            x1, x2, x3 = p["coords"]
142
+            pt = self.to_svg(x1, x2, x3)
143
+
144
+            # Punktkreuz zeichnen (Größe: 4px Armlänge, rot hervorgehoben für Fokus)
145
+            dwg.add(dwg.line((pt[0] - 4, pt[1] - 4), (pt[0] + 4, pt[1] + 4), stroke='red', stroke_width=1.5))
146
+            dwg.add(dwg.line((pt[0] - 4, pt[1] + 4), (pt[0] + 4, pt[1] - 4), stroke='red', stroke_width=1.5))
147
+
148
+            # Beschriftungslogik auswerten
149
+            if self.show_labels:
150
+                if self.show_coords:
151
+                    # Format: A(2|4|3) - wir formatieren eventuelle Floats sauber weg, wenn es Ganzzahlen sind
152
+                    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
+                    label_text = f"{p['name']}{fmt_coords}"
154
+                else:
155
+                    # Nur Name: A
156
+                    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
+                ))
163
+
164
+        return dwg.tostring()
0 165
\ No newline at end of file