| 1 | <?php |
| 2 | /** |
| 3 | * AutoCons – Konstruktion für die Bekleidungsindustrie |
| 4 | * Version 0.4 (2025-05-29) |
| 5 | * |
| 6 | * PHP-Umsetzung von grafproc.prg + WRK.PRG |
| 7 | * Original-Konstruktion: Optikon-Methode (Hochschule Mönchengladbach) |
| 8 | * Kurvendaten: curves.dbf (Optikon-Oberteil) |
| 9 | * |
| 10 | * Vorderteil (VT) + Rückenteil (RT) |
| 11 | * SVG-Ausgabe mit kubischem Hermite-Spline (klassisch) |
| 12 | */ |
| 13 | |
| 14 | // ---- Quellcode-Anzeige (?source=1) ---- |
| 15 | if (isset($_GET['source'])) { |
| 16 | $dl = isset($_GET['dl']); |
| 17 | if ($dl) { |
| 18 | header('Content-Type: text/plain; charset=utf-8'); |
| 19 | header('Content-Disposition: attachment; filename="autocons.php"'); |
| 20 | readfile(__FILE__); |
| 21 | exit; |
| 22 | } |
| 23 | header('Content-Type: text/html; charset=utf-8'); |
| 24 | $raw = file_get_contents(__FILE__); |
| 25 | $lines = explode("\n", $raw); |
| 26 | $total = count($lines); |
| 27 | $size = number_format(filesize(__FILE__)); |
| 28 | echo '<!DOCTYPE html><html lang="de"><head><meta charset="utf-8">'; |
| 29 | echo '<title>AutoCons â Quellcode</title>'; |
| 30 | echo '<style> |
| 31 | @import url("https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&display=swap"); |
| 32 | *{box-sizing:border-box;margin:0;padding:0} |
| 33 | body{background:#1a1209;color:#d4c9a8;font-family:"DM Mono",monospace;font-size:12.5px;line-height:1.55} |
| 34 | header{background:#0e0c07;padding:.7rem 1.5rem;display:flex;align-items:center;gap:1.5rem; |
| 35 | border-bottom:1px solid #3a3020;position:sticky;top:0;z-index:10;flex-wrap:wrap} |
| 36 | h1{color:#d4c9a8;font-size:.95rem;font-weight:500} |
| 37 | .info{font-size:.72rem;color:#7a6a4a} |
| 38 | a.btn{color:#fff;background:#3a5a8a;padding:.28rem .65rem;border-radius:2px; |
| 39 | font-size:.75rem;text-decoration:none} |
| 40 | a.btn:hover{background:#5578aa} |
| 41 | a.back{color:#7a9abf;font-size:.8rem;text-decoration:none} |
| 42 | a.back:hover{color:#9ab} |
| 43 | table{border-collapse:collapse;width:100%} |
| 44 | .ln{width:3.8em;text-align:right;padding:.02rem .8rem .02rem .3rem; |
| 45 | color:#3a3020;user-select:none;border-right:1px solid #2a2010;vertical-align:top; |
| 46 | font-size:11px} |
| 47 | .lc{padding:.02rem .5rem .02rem .9rem;white-space:pre-wrap;word-break:break-all} |
| 48 | tr:target .ln,.tr:target .lc{background:#2a1e10} |
| 49 | tr:hover .ln{color:#6a5a3a} |
| 50 | tr:hover .lc{background:#1e140a} |
| 51 | </style></head><body><header>'; |
| 52 | echo '<h1>ð AutoCons â PHP-Quellcode</h1>'; |
| 53 | echo "<span class='info'>$total Zeilen · $size Bytes</span>"; |
| 54 | echo '<a class="back" href="?">← Zurück</a>'; |
| 55 | echo '<a class="btn" href="?source=1&dl=1">⬠Download autocons.php</a>'; |
| 56 | echo '</header><table>'; |
| 57 | foreach ($lines as $i => $raw_line) { |
| 58 | $nr = $i + 1; |
| 59 | $lc = htmlspecialchars($raw_line); |
| 60 | // Kommentare |
| 61 | $lc = preg_replace('/^(\s*\/\/.*)$/', '<span style="color:#5a7a4a">$1</span>', $lc); |
| 62 | $lc = preg_replace('/^(\s*\*.*)$/', '<span style="color:#5a7a4a">$1</span>', $lc); |
| 63 | // Strings |
| 64 | // String-Hervorhebung (einfache Anführungszeichen) |
| 65 | $lc = preg_replace('/"([^&]*)"/','<span style="color:#a09060">"$1"</span>', $lc); |
| 66 | // PHP-Keywords |
| 67 | foreach (['function','return','global','if','else','foreach', |
| 68 | 'for','while','switch','case','break','echo','exit', |
| 69 | 'include','header','array_fill','array_merge'] as $kw) |
| 70 | $lc = preg_replace('/\b('.preg_quote($kw,'/').')\b/', |
| 71 | '<span style="color:#6a9abf">$1</span>', $lc); |
| 72 | // Geometriefunktionen |
| 73 | $lc = preg_replace('/\b(PW|PPW|LPP|PLL|LSP|LPDP|LPA|LPAV|KR|PKL|PKK|PZP|PAL|PALV|PLS|PD|AD|AX|AY)(?=\s*\()/', |
| 74 | '<span style="color:#c8a040">$1</span>', $lc); |
| 75 | echo "<tr id='L$nr'><td class='ln'>$nr</td><td class='lc'>$lc</td></tr>\n"; |
| 76 | } |
| 77 | echo '</table></body></html>'; |
| 78 | exit; |
| 79 | } |
| 80 | |
| 81 | // ============================================================ |
| 82 | // Datenstrukturen |
| 83 | // ============================================================ |
| 84 | $ANZPKT = 300; |
| 85 | $ANZLIN = 50; |
| 86 | $ANZKR = 20; |
| 87 | $virgo = 1.23456789e15; |
| 88 | $P_ = array_fill(0, $ANZPKT, [$virgo, $virgo]); |
| 89 | $L_ = array_fill(0, $ANZLIN, [$virgo, $virgo, $virgo]); |
| 90 | $K_ = array_fill(0, $ANZKR, [$virgo, $virgo, $virgo]); |
| 91 | $log = []; |
| 92 | $steps = []; // Schritt-für-Schritt-Snapshots |
| 93 | |
| 94 | // Snapshot nach jedem Konstruktionsschritt speichern |
| 95 | // Aktueller Konstruktionsteil: 'VT' oder 'RT' |
| 96 | $currentPart = 'VT'; |
| 97 | |
| 98 | function snapshot(string $fn, string $desc) { |
| 99 | global $P_, $L_, $K_, $steps, $virgo, $currentPart; |
| 100 | // Nur definierte Punkte speichern (reduziert JSON-Größe) |
| 101 | $pts = []; |
| 102 | foreach ($P_ as $i => $p) { |
| 103 | if ($p[0] < 1e14) $pts[$i] = [round($p[0],4), round($p[1],4)]; |
| 104 | } |
| 105 | $lns = []; |
| 106 | foreach ($L_ as $i => $l) { |
| 107 | if ($l[0] < 1e14) $lns[$i] = [round($l[0],6), round($l[1],6), round($l[2],6)]; |
| 108 | } |
| 109 | // Kreise mit Teil-Tag speichern (für korrekte SVG-Positionierung) |
| 110 | $krs = []; |
| 111 | foreach ($K_ as $i => $k) { |
| 112 | if ($k[0] < 1e14) $krs[$i] = [round($k[0],4), round($k[1],4), round($k[2],4), $currentPart]; |
| 113 | } |
| 114 | $steps[] = ['fn'=>$fn, 'desc'=>$desc, 'part'=>$currentPart, 'P'=>$pts, 'L'=>$lns, 'K'=>$krs]; |
| 115 | } |
| 116 | |
| 117 | // ============================================================ |
| 118 | // Hilfsfunktionen |
| 119 | // ============================================================ |
| 120 | function sqr($a) { return $a * $a; } |
| 121 | function logMsg($msg) { global $log; $log[] = $msg; } |
| 122 | |
| 123 | // ============================================================ |
| 124 | // Geometrische Grundfunktionen (aus grafproc.prg) |
| 125 | // ============================================================ |
| 126 | function AX($p1,$p2){global $P_;return abs($P_[$p1][0]-$P_[$p2][0]);} |
| 127 | function AY($p1,$p2){global $P_;return abs($P_[$p1][1]-$P_[$p2][1]);} |
| 128 | function AD($p1,$p2){global $P_;return sqrt(sqr($P_[$p2][0]-$P_[$p1][0])+sqr($P_[$p2][1]-$P_[$p1][1]));} |
| 129 | function SignR($r){return ($r>=0)?1.0:-1.0;} |
| 130 | |
| 131 | function PW($pnr,$x,$y){ |
| 132 | global $P_; |
| 133 | $P_[$pnr][0]=$x; $P_[$pnr][1]=$y; |
| 134 | $desc="PW($pnr, ".round($x,3).", ".round($y,3).")"; |
| 135 | logMsg($desc); |
| 136 | snapshot('PW', $desc); |
| 137 | } |
| 138 | function PPW($pnr1,$pnr2,$dx,$dy){ |
| 139 | global $P_; |
| 140 | $P_[$pnr1][0]=$P_[$pnr2][0]+$dx; |
| 141 | $P_[$pnr1][1]=$P_[$pnr2][1]+$dy; |
| 142 | $desc="PPW($pnr1 <- $pnr2 +(".round($dx,3).",".round($dy,3).")) => (".round($P_[$pnr1][0],3).",".round($P_[$pnr1][1],3).")"; |
| 143 | logMsg($desc); |
| 144 | snapshot('PPW', $desc); |
| 145 | } |
| 146 | function RTG($mp,$pa,$pb){ |
| 147 | global $P_; |
| 148 | $R=($P_[$pa][0]-$P_[$mp][0])*($P_[$pb][1]-$P_[$mp][1]) |
| 149 | -($P_[$pa][1]-$P_[$mp][1])*($P_[$pb][0]-$P_[$mp][0]); |
| 150 | return ($R>0)?-1:(($R<0)?1:0); |
| 151 | } |
| 152 | function LPP($lnr,$p1,$p2){ |
| 153 | global $P_,$L_; |
| 154 | $L_[$lnr][0]=$P_[$p1][1]-$P_[$p2][1]; |
| 155 | $L_[$lnr][1]=$P_[$p2][0]-$P_[$p1][0]; |
| 156 | $L_[$lnr][2]=$P_[$p2][1]*$P_[$p1][0]-$P_[$p1][1]*$P_[$p2][0]; |
| 157 | if($L_[$lnr][0]<0){$L_[$lnr][0]*=-1;$L_[$lnr][1]*=-1;$L_[$lnr][2]*=-1;} |
| 158 | logMsg("LPP(L$lnr durch P$p1 und P$p2)"); |
| 159 | snapshot('LPP', "LPP(L$lnr durch P$p1, P$p2)"); |
| 160 | } |
| 161 | function KR($knr,$pnr,$radi){ |
| 162 | global $P_,$K_; |
| 163 | $K_[$knr][0]=$P_[$pnr][0]; $K_[$knr][1]=$P_[$pnr][1]; $K_[$knr][2]=$radi; |
| 164 | logMsg("KR(K$knr Mittelpunkt P$pnr, r=".round($radi,3).")"); |
| 165 | snapshot('KR', "KR(K$knr um P$pnr, r=".round($radi,3).")"); |
| 166 | } |
| 167 | function PLL($pnr,$l1,$l2){ |
| 168 | global $P_,$L_; |
| 169 | $det=$L_[$l1][0]*$L_[$l2][1]-$L_[$l2][0]*$L_[$l1][1]; |
| 170 | if(abs($det)<1e-10){logMsg("PLL: parallel ($l1,$l2)");return;} |
| 171 | $P_[$pnr][0]=($L_[$l1][1]*$L_[$l2][2]-$L_[$l2][1]*$L_[$l1][2])/$det; |
| 172 | $P_[$pnr][1]=($L_[$l1][2]*$L_[$l2][0]-$L_[$l2][2]*$L_[$l1][0])/$det; |
| 173 | logMsg("PLL(P$pnr = Schnitt L$l1/L$l2) => (".round($P_[$pnr][0],3).",".round($P_[$pnr][1],3).")"); |
| 174 | snapshot('PLL', "PLL(P$pnr = L$l1 ∩ L$l2) => (".round($P_[$pnr][0],3).",".round($P_[$pnr][1],3).")"); |
| 175 | } |
| 176 | function PZP($pneu,$p1,$p2,$prz){ |
| 177 | global $P_; |
| 178 | $P_[$pneu][0]=$P_[$p1][0]+($P_[$p2][0]-$P_[$p1][0])*$prz/100; |
| 179 | $P_[$pneu][1]=$P_[$p1][1]+($P_[$p2][1]-$P_[$p1][1])*$prz/100; |
| 180 | logMsg('PZP(P'.$pneu.' auf P'.$p1.'..P'.$p2.', '.round($prz,1).'%)'); |
| 181 | snapshot('PZP', 'PZP(P'.$pneu.' auf P'.$p1.'..P'.$p2.', '.round($prz,1).'%)'); |
| 182 | } |
| 183 | function LSP($l2,$pnr,$l1){ |
| 184 | global $P_,$L_; |
| 185 | $L_[$l2][0]=-$L_[$l1][1]; $L_[$l2][1]=$L_[$l1][0]; |
| 186 | $L_[$l2][2]=$L_[$l1][1]*$P_[$pnr][0]-$L_[$l1][0]*$P_[$pnr][1]; |
| 187 | if($L_[$l2][0]<0){$L_[$l2][0]*=-1;$L_[$l2][1]*=-1;$L_[$l2][2]*=-1;} |
| 188 | logMsg("LSP(L$l2 senkrecht zu L$l1 durch P$pnr)"); |
| 189 | snapshot('LSP', "LSP(L$l2 ⊥ L$l1 durch P$pnr)"); |
| 190 | } |
| 191 | function LPDP($l2,$pnr,$l1){ |
| 192 | global $P_,$L_; |
| 193 | $L_[$l2][0]=$L_[$l1][0]; $L_[$l2][1]=$L_[$l1][1]; |
| 194 | $L_[$l2][2]=-$L_[$l2][0]*$P_[$pnr][0]-$L_[$l2][1]*$P_[$pnr][1]; |
| 195 | if($L_[$l2][0]<0){$L_[$l2][0]*=-1;$L_[$l2][1]*=-1;$L_[$l2][2]*=-1;} |
| 196 | logMsg("LPDP(L$l2 parallel zu L$l1 durch P$pnr)"); |
| 197 | snapshot('LPDP', "LPDP(L$l2 ∥ L$l1 durch P$pnr)"); |
| 198 | } |
| 199 | function PSL($pN,$pA,$lB){ |
| 200 | global $P_,$L_; |
| 201 | $A2=-$L_[$lB][1]; $B2=$L_[$lB][0]; |
| 202 | $C2=$L_[$lB][1]*$P_[$pA][0]-$L_[$lB][0]*$P_[$pA][1]; |
| 203 | $det=$L_[$lB][0]*$B2-$A2*$L_[$lB][1]; |
| 204 | if(abs($det)<1e-10){logMsg("PSL: det=0");return;} |
| 205 | $P_[$pN][0]=($L_[$lB][1]*$C2-$B2*$L_[$lB][2])/$det; |
| 206 | $P_[$pN][1]=($L_[$lB][2]*$A2-$C2*$L_[$lB][0])/$det; |
| 207 | } |
| 208 | function LPA($l2,$abst,$l1){ |
| 209 | global $L_; |
| 210 | $L_[$l2][0]=$L_[$l1][0]; $L_[$l2][1]=$L_[$l1][1]; |
| 211 | if($abst==0.0){$L_[$l2][2]=$L_[$l1][2];} |
| 212 | elseif($L_[$l1][0]==0){$L_[$l2][2]=$L_[$l1][2]-$abst*$L_[$l1][1];} |
| 213 | elseif($L_[$l1][1]!=0.0){$y=$abst*sqrt(1+sqr($L_[$l1][0]/$L_[$l1][1]));$L_[$l2][2]=$L_[$l1][2]-$y*$L_[$l1][1];} |
| 214 | else{$L_[$l2][2]=$L_[$l1][2]-$abst*$L_[$l1][0];} |
| 215 | if($L_[$l2][0]<0){$L_[$l2][0]*=-1;$L_[$l2][1]*=-1;$L_[$l2][2]*=-1;} |
| 216 | logMsg("LPA(L$l2 parallel zu L$l1, Abstand=".round($abst,3).")"); |
| 217 | snapshot('LPA', "LPA(L$l2 ∥ L$l1, Abst=".round($abst,3).")"); |
| 218 | } |
| 219 | function LPAV($l2,$abst,$l1){ |
| 220 | global $L_; |
| 221 | $L_[$l2][0]=$L_[$l1][0]; $L_[$l2][1]=$L_[$l1][1]; |
| 222 | if($abst==0.0){$L_[$l2][2]=$L_[$l1][2];} |
| 223 | elseif($L_[$l1][0]==0){$L_[$l2][2]=$L_[$l1][2]-$abst*$L_[$l1][1];} |
| 224 | elseif($L_[$l1][1]!=0){ |
| 225 | $y=$abst*sqrt(1+sqr($L_[$l1][0]/$L_[$l1][1])); |
| 226 | if(-$L_[$l1][0]/$L_[$l1][1]>0)$y=-$y; |
| 227 | $L_[$l2][2]=$L_[$l1][2]-$y*$L_[$l1][1]; |
| 228 | }else{$L_[$l2][2]=$L_[$l1][2]-$abst*$L_[$l1][0];} |
| 229 | if($L_[$l2][0]<0){$L_[$l2][0]*=-1;$L_[$l2][1]*=-1;$L_[$l2][2]*=-1;} |
| 230 | } |
| 231 | function PAL($Pnr,$altPnr,$abst,$Lnr){ |
| 232 | global $L_; |
| 233 | if($abst==0){PPW($Pnr,$altPnr,0,0);return;} |
| 234 | $save=[];for($i=1;$i<=3;$i++)$save[$i]=$L_[$i]; |
| 235 | LPA(3,0,$Lnr);LSP(1,$altPnr,3);LPA(2,$abst,3);PLL($Pnr,1,2); |
| 236 | for($i=1;$i<=3;$i++)$L_[$i]=$save[$i]; |
| 237 | } |
| 238 | function PALV($Pnr,$altPnr,$abst,$Lnr){ |
| 239 | global $L_; |
| 240 | if($abst==0){PPW($Pnr,$altPnr,0,0);return;} |
| 241 | $save=[];for($i=1;$i<=3;$i++)$save[$i]=$L_[$i]; |
| 242 | LPA(3,0,$Lnr);LSP(1,$altPnr,3);LPAV(2,$abst,3);PLL($Pnr,1,2); |
| 243 | for($i=1;$i<=3;$i++)$L_[$i]=$save[$i]; |
| 244 | } |
| 245 | function PLS($pN,$pA,$lB){ |
| 246 | global $P_; |
| 247 | PSL($pN,$pA,$lB); |
| 248 | $P_[$pN][0]=2*$P_[$pN][0]-$P_[$pA][0]; |
| 249 | $P_[$pN][1]=2*$P_[$pN][1]-$P_[$pA][1]; |
| 250 | } |
| 251 | function PKL($pnr,$knr,$lnr,$sp=1){ |
| 252 | global $P_,$L_,$K_; |
| 253 | $A1=$L_[$lnr][0];$B1=$L_[$lnr][1];$C1=$L_[$lnr][2]; |
| 254 | $XM=$K_[$knr][0];$YM=$K_[$knr][1];$R=$K_[$knr][2]; |
| 255 | $A2=-$B1;$B2=$A1;$C2=$B1*$XM-$A1*$YM; |
| 256 | $D=$A1*$A1+$B1*$B1; |
| 257 | $X0=(-$A1*$C1+$B1*$C2)/$D;$Y0=(-$A1*$C2-$B1*$C1)/$D; |
| 258 | $PR=sqr($X0-$XM)+sqr($Y0-$YM);$R1=$R*$R-$PR;$EPS=0.0005*$R; |
| 259 | if(abs($R1)<=$EPS){$P_[$pnr][0]=$X0;$P_[$pnr][1]=$Y0; |
| 260 | snapshot('PKL', "PKL(P$pnr = K$knr ∩ L$lnr, Tangente)"); return 1;} |
| 261 | if($R1<=0){logMsg("PKL: kein Schnitt");return 0;} |
| 262 | $H=sqrt($R1);$D1=sqrt($D);$C=$B1/$D1;$D2=-$A1/$D1; |
| 263 | if($sp==1){$P_[$pnr][0]=$X0+$H*$C;$P_[$pnr][1]=$Y0+$H*$D2;} |
| 264 | else {$P_[$pnr][0]=$X0-$H*$C;$P_[$pnr][1]=$Y0-$H*$D2;} |
| 265 | snapshot('PKL', "PKL(P$pnr = K$knr ∩ L$lnr, sp=$sp) => (".round($P_[$pnr][0],3).",".round($P_[$pnr][1],3).")"); |
| 266 | return 2; |
| 267 | } |
| 268 | function PKK($pnr,$k1,$k2,$sp=1){ |
| 269 | global $P_,$K_; |
| 270 | $XM1=$K_[$k1][0];$YM1=$K_[$k1][1];$R1=$K_[$k1][2]; |
| 271 | $XM2=$K_[$k2][0];$YM2=$K_[$k2][1];$R2=$K_[$k2][2]; |
| 272 | $A=sqrt(sqr($XM2-$XM1)+sqr($YM2-$YM1)); |
| 273 | if($A<1e-10||$A>=$R1+$R2||$A<=abs($R1-$R2)){logMsg("PKK: kein Schnitt ($k1,$k2)");return;} |
| 274 | $H=sqrt(max(0,sqr($R1)*sqr($R2)*4-sqr(sqr($A)-sqr($R1)-sqr($R2))))/2/$A; |
| 275 | $AX_=($XM2-$XM1)/$A;$AY_=($YM2-$YM1)/$A; |
| 276 | $A1_=sqrt(max(0,sqr($R1)-sqr($H))); |
| 277 | if($sp==1){$P_[$pnr][0]=$XM1+$A1_*$AX_-$H*$AY_;$P_[$pnr][1]=$YM1+$A1_*$AY_+$H*$AX_;} |
| 278 | else {$P_[$pnr][0]=$XM1+$A1_*$AX_+$H*$AY_;$P_[$pnr][1]=$YM1+$A1_*$AY_-$H*$AX_;} |
| 279 | logMsg("PKK(P$pnr = Schnitt K$k1/K$k2, sp=$sp) => (".round($P_[$pnr][0],3).",".round($P_[$pnr][1],3).")"); |
| 280 | snapshot('PKK', "PKK(P$pnr = K$k1 ∩ K$k2, sp=$sp) => (".round($P_[$pnr][0],3).",".round($P_[$pnr][1],3).")"); |
| 281 | } |
| 282 | function PD($pneu,$palt,$mp,$betrag,$winkel='W',$sep=' '){ |
| 283 | global $P_; |
| 284 | if($betrag==0.0){PPW($pneu,$palt,0,0);return;} |
| 285 | if(strtoupper($winkel)=='W'){ |
| 286 | $Bogen=-2*M_PI*$betrag/360.0; |
| 287 | }else{ |
| 288 | $d=AD($mp,$palt); |
| 289 | if($d<1e-10){PPW($pneu,$palt,0,0);return;} |
| 290 | $R=$betrag/(2*$d); |
| 291 | $Bogen=-2*atan($R/sqrt(max(1-$R*$R,1e-15))); |
| 292 | } |
| 293 | $x=$P_[$palt][0]-$P_[$mp][0];$y=$P_[$palt][1]-$P_[$mp][1]; |
| 294 | $xr=$x*cos($Bogen)-$y*sin($Bogen);$yr=$x*sin($Bogen)+$y*cos($Bogen); |
| 295 | $sep=strtoupper($sep); |
| 296 | if($sep=='X'){$P_[$pneu][0]=$xr+$P_[$mp][0];$P_[$pneu][1]=$P_[$palt][1];} |
| 297 | elseif($sep=='Y'){$P_[$pneu][0]=$P_[$palt][0];$P_[$pneu][1]=$yr+$P_[$mp][1];} |
| 298 | else{$P_[$pneu][0]=$xr+$P_[$mp][0];$P_[$pneu][1]=$yr+$P_[$mp][1];} |
| 299 | snapshot('PD', "PD(P$pneu = P$palt gedreht um P$mp, ".round($betrag,2)."°)"); |
| 300 | } |
| 301 | |
| 302 | // ============================================================ |
| 303 | // Catmull-Rom Spline → SVG cubic bezier path |
| 304 | // ============================================================ |
| 305 | $defaults = [ |
| 306 | 'uBr'=>88.0,'uGe'=>94.0,'lKoe'=>168.0, |
| 307 | 'uHg'=>16.0,'uTa'=>68.0,'lTV'=>0.0, |
| 308 | 'uOa'=>28.0,'uHa'=>36.0,'lGe'=>20.0, |
| 309 | 'lAr'=>60.0,'lOa'=>34.0,'lTa'=>40.0, |
| 310 | 'lVo'=>42.0,'lBr'=>25.0,'lRue'=>38.0, |
| 311 | 'lSu'=>12.5,'alfaSu'=>22.0, |
| 312 | 'zBr'=>12.0,'hPo'=>0.50, |
| 313 | 'bTAv'=>2.00,'bTAh'=>2.50, |
| 314 | 'VHtv'=>-1.20,'VHth'=>0.80, |
| 315 | 'VHh'=>1.50,'bSuM'=>1.00, |
| 316 | 'bUb'=>2.00,'hSaT'=>-10.0, |
| 317 | ]; |
| 318 | |
| 319 | // POST-Werte übernehmen |
| 320 | $vals = $defaults; |
| 321 | if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') { |
| 322 | foreach ($defaults as $k=>$def) |
| 323 | if (isset($_POST[$k]) && is_numeric($_POST[$k])) |
| 324 | $vals[$k]=(float)$_POST[$k]; |
| 325 | } |
| 326 | |
| 327 | // Kurzformen |
| 328 | $uBr_=$vals['uBr'];$uGe_=$vals['uGe'];$lKoe_=$vals['lKoe']; |
| 329 | $uHg_=$vals['uHg'];$uTa_=$vals['uTa'];$lTV_=$vals['lTV']; |
| 330 | $uOa_=$vals['uOa'];$uHa_=$vals['uHa'];$lGe_=$vals['lGe']; |
| 331 | $lAr_=$vals['lAr'];$lOa_=$vals['lOa'];$lTa_=$vals['lTa']; |
| 332 | $lVo_=$vals['lVo'];$lBr_=$vals['lBr'];$lRue_=$vals['lRue']; |
| 333 | $lSu_=$vals['lSu'];$alfaSu_=$vals['alfaSu']; |
| 334 | $zBr=$vals['zBr'];$hPo=$vals['hPo']; |
| 335 | $bTAv=$vals['bTAv'];$bTAh=$vals['bTAh']; |
| 336 | $VHtv=$vals['VHtv'];$VHth=$vals['VHth'];$VHh=$vals['VHh']; |
| 337 | $bSuM=$vals['bSuM'];$bUb=$vals['bUb'];$hSaT=$vals['hSaT']; |
| 338 | |
| 339 | // ============================================================ |
| 340 | // VORDERTEIL (VT) |
| 341 | // ============================================================ |
| 342 | logMsg("=== VORDERTEIL (VT) ==="); $currentPart = 'VT'; |
| 343 | define('VT',100); |
| 344 | $A=100;$B=101;$C=102;$D=103;$E=104;$F=105;$G=106;$H=107; |
| 345 | $I=108;$J=109;$K=110;$L=111;$M=112;$N=113;$O=114; |
| 346 | $P1=115;$P2=128;$P3=133;$P4=134;$P5=135;$P6=136;$P7=137; |
| 347 | $P9=138;$P10=116;$P11=117;$P11i=119;$P11a=118;$P12=120; |
| 348 | $P13=121;$P14=122;$P15=123;$P16=124;$P17=125;$P18=126; |
| 349 | $P19=127;$P20=129;$P21=130;$P22=131;$P23=132;$PPKT=139; |
| 350 | |
| 351 | $T=($uGe_*10-430*$uBr_/48-280/3-abs(10*$uBr_-1040)/48)/10; |
| 352 | $hBr=(30*$lKoe_/40+125+(10*$uBr_-920)/20)/10; |
| 353 | $hAV=$zBr/4;$hTa=$lTa_-$lTV_; |
| 354 | PW($A,0.0,0.0); |
| 355 | $bVo=(10*$uBr_/5+5+3*10*$zBr/20)/10; |
| 356 | PPW($B,$A,$bVo,0.0); |
| 357 | PPW($C,$B,0.0,-($hTa-$hBr)); |
| 358 | PW($D,$P_[$A][0],$P_[$C][1]); |
| 359 | $hVo=$lVo_-$lTV_; |
| 360 | PPW($E,$D,0.0,$hVo); |
| 361 | $bHlv=$uHa_/6+$zBr/40; |
| 362 | PPW($F,$E,$bHlv,0.0); |
| 363 | PPW($G,$A,(10*$uBr_/10+2+10*$zBr/20)/10,0.0); |
| 364 | $betaBA=10*$uBr_/40-5.4-abs(10*$uBr_-1040)/200; |
| 365 | PD($H,$F,$G,$betaBA,'W',' '); |
| 366 | $l1=(3*10*$uBr_/40+140)/10; |
| 367 | PPW($I,$B,0.0,$hBr-($l1+$hAV)); |
| 368 | $hAVv=$uBr_/20+0.1+$zBr/20;$bAlv=$hAVv; |
| 369 | PPW($J,$I,0.0,$hAVv); |
| 370 | PPW($K,$I,$bAlv,0.0); |
| 371 | PPW($L,$J,$bAlv,0.0); |
| 372 | $betaSuv=$betaBA+$alfaSu_-3-2*10*$hPo/5; |
| 373 | $bSuv=(10*$lSu_+3*(10*$uBr_-920)/40-1+10*$zBr/10)/10; |
| 374 | PPW(1,$H,$bSuv,0.0); |
| 375 | PD($M,1,$H,$betaSuv,'W',' '); |
| 376 | $hHlv=$uHa_/3-4.4+$zBr/40; |
| 377 | PPW($N,$E,0.0,-$hHlv); |
| 378 | PPW($O,$N,0.0,$bHlv); |
| 379 | PPW($PPKT,$O,$bHlv,0.0); |
| 380 | PW($P1,$P_[$K][0],$P_[$D][1]); |
| 381 | PPW($P3,$D,0.0,-$lGe_); |
| 382 | PW($P12,$P_[$G][0],$P_[$P3][1]); |
| 383 | PW($P2,$P_[$P1][0],$P_[$P3][1]); |
| 384 | PPW($P15,$P2,1.0,0.0); |
| 385 | PPW($P13,$P12,0.0,$lGe_/2); |
| 386 | PW($P11,$P_[$G][0],$P_[$D][1]); |
| 387 | PPW($P11i,$P11,-$bTAv/2,0.0); |
| 388 | PPW($P11a,$P11,+$bTAv/2,0.0); |
| 389 | PPW($P14,$P1,-1.0,0.0); |
| 390 | LPP(1,$P14,$P15); |
| 391 | PPW($P16,$D,0.0,$hSaT); |
| 392 | PPW(1,$P1,0.0,$hSaT); |
| 393 | LPP(2,$P16,1); |
| 394 | LPP(3,$P13,$P11a); |
| 395 | LPP(4,$P13,$P11i); |
| 396 | PLL($P17,2,4); |
| 397 | PLL($P18,2,3); |
| 398 | PLL($P19,1,2); |
| 399 | PPW($P6,$N,0.0,$VHtv); |
| 400 | PD($P9,$P6,$O,-45.0,'W',' '); |
| 401 | PPW($P20,$P6,-$bUb,0.0); |
| 402 | PPW($P21,$A,-$bUb,0.0); |
| 403 | PPW($P22,$D,-$bUb,0.0); |
| 404 | PPW($P23,$P16,-$bUb,0.0); |
| 405 | $l1=$bSuv/2; |
| 406 | PZP($P4,$H,$M,$l1/AD($H,$M)*100); |
| 407 | KR(1,$G,AD($G,$P4)); |
| 408 | KR(2,$F,$bSuv/2); |
| 409 | PKK(1,1,2,1);PKK(2,1,2,2); |
| 410 | if($P_[1][0]>$P_[2][0])PPW($P5,1,0.0,0.0);else PPW($P5,2,0.0,0.0); |
| 411 | PZP($P7,$F,$P5,$VHh/AD($F,$P5)*100); |
| 412 | PZP($P10,$H,$M,(AD($H,$M)+$bSuM)/AD($H,$M)*100); |
| 413 | |
| 414 | $vt_contour=[ |
| 415 | ['nr'=>$P23,'lbl'=>'P23','info'=>''], |
| 416 | ['nr'=>$P20,'lbl'=>'P20','info'=>''], |
| 417 | ['nr'=>$P6, 'lbl'=>'P6', 'info'=>'A'], |
| 418 | ['nr'=>$P9, 'lbl'=>'P9', 'info'=>'X'], |
| 419 | ['nr'=>$P7, 'lbl'=>'P7', 'info'=>'E'], |
| 420 | ['nr'=>$P5, 'lbl'=>'P5', 'info'=>'K'], |
| 421 | ['nr'=>$P4, 'lbl'=>'P4', 'info'=>'K'], |
| 422 | ['nr'=>$P10,'lbl'=>'P10','info'=>'A'], |
| 423 | ['nr'=>$J, 'lbl'=>'J', 'info'=>'X'], |
| 424 | ['nr'=>$K, 'lbl'=>'K', 'info'=>'E'], |
| 425 | ['nr'=>$P14,'lbl'=>'P14','info'=>'K'], |
| 426 | ['nr'=>$P19,'lbl'=>'P19','info'=>''], |
| 427 | ['nr'=>$P18,'lbl'=>'P18','info'=>'K'], |
| 428 | ['nr'=>$P11a,'lbl'=>'P11a','info'=>''], |
| 429 | ['nr'=>$G, 'lbl'=>'G', 'info'=>''], |
| 430 | ['nr'=>$P11i,'lbl'=>'P11i','info'=>''], |
| 431 | ['nr'=>$P17,'lbl'=>'P17','info'=>'K'], |
| 432 | ['nr'=>$P16,'lbl'=>'P16','info'=>''], |
| 433 | ['nr'=>$P23,'lbl'=>'','info'=>''], |
| 434 | ]; |
| 435 | $vt_internal=[[$P6,$P16],[$A,$B],[$P5,$G],[$P4,$G]]; |
| 436 | |
| 437 | // ============================================================ |
| 438 | // RÜCKENTEIL (RT) |
| 439 | // ============================================================ |
| 440 | logMsg("=== RÜCKENTEIL (RT) ==="); $currentPart = 'RT'; |
| 441 | $RA=200;$RB=201;$RC=202;$RD=203;$RE=204;$RF=205;$RG=206; |
| 442 | $RH=207;$RI=208;$RJ=209;$RK=210;$RL=211;$RM=212;$RN=213;$RO=214; |
| 443 | $RP1=215;$RP2=228;$RP3=235;$RP4=236;$RP5=237;$RP6=238; |
| 444 | $RP7=239;$RP8=240;$RP9A=241;$RP9I=242; |
| 445 | $RP10=216;$RP11=217;$RP12=218;$RP13=219;$RP14=220;$RP15=221; |
| 446 | $RP16=222;$RP17=223;$RP18=224;$RP18A=225;$RP18I=226; |
| 447 | $RP19=227;$RP20=229;$RP21=230;$RP22=231;$RP23=232; |
| 448 | $RP24=233;$RP25=234; |
| 449 | $LMih=20; |
| 450 | |
| 451 | $T=($uGe_*10-430*$uBr_/48-280/3-abs(10*$uBr_-1040)/48)/10; |
| 452 | $hBr=(30*$lKoe_/40+125+(10*$uBr_-920)/20)/10; |
| 453 | $hAV=$zBr/4;$hTa=$lTa_-$lTV_; |
| 454 | $li=(3*10*$uBr_/40+140+10*$hAV)/10; |
| 455 | PW($RA,0.0,0.0); |
| 456 | PPW($RB,$RA,0.0,$hBr); |
| 457 | PPW($RC,$RB,0.0,-$hTa); |
| 458 | $bRAo=1.3; |
| 459 | PPW($RD,$RC,-$bRAo,0.0); |
| 460 | LPP($LMih,$RD,$RB); |
| 461 | $bHlh=(10*$uHa_/6+8+10*$zBr/40)/10; |
| 462 | PPW($RE,$RB,-$bHlh,0.0); |
| 463 | $hHlh=(10*$uHa_/6-39+3*10*$zBr/200)/10; |
| 464 | PPW($RF,$RE,0.0,$hHlh); |
| 465 | LPP(1,$RA,$RB); |
| 466 | LSP(2,$RA,1); |
| 467 | PLL($RG,$LMih,2); |
| 468 | $bRue=(10*$uBr_/8+56+10*$zBr/8)/10; |
| 469 | PPW($RH,$RG,-$bRue,0.0); |
| 470 | PW($RI,$P_[$RH][0],$P_[$RB][1]-$li); |
| 471 | PPW($RJ,$RI,0.0,(10*$uBr_/20-6+10*$zBr/20)/10); |
| 472 | $bAlh=(10*$uBr_/8-62+7*10*$zBr/40)/10; |
| 473 | PPW($RK,$RI,-$bAlh,0.0); |
| 474 | PPW($RL,$RJ,-6/10,0.0); |
| 475 | $bSuh=(10*$lSu_+7*10*$uBr_/80-57.5+10*$zBr/10)/10; |
| 476 | PPW(1,$RF,-$bSuh,0.0); |
| 477 | $betaSuh=$alfaSu_-4-2*10*$hPo/5;$wi=-$betaSuh; |
| 478 | PD($RM,1,$RF,$wi,'W',' '); |
| 479 | $hAVh=(10*$uBr_/20+43+10*$zBr/20)/10; |
| 480 | PPW($RN,$RI,0.0,$hAVh); |
| 481 | $betaHl=2*$alfaSu_-7;$wi=-$betaHl; |
| 482 | $bHlv_rt=(10*$uHa_/6+10*$zBr/40)/10; |
| 483 | PPW(1,$RF,$bHlv_rt,0.0); |
| 484 | PD($RO,1,$RF,$wi,'W',' '); |
| 485 | PW($RP1,$P_[$RK][0],$P_[$RC][1]); |
| 486 | PW($RP2,$P_[$RK][0],$P_[$RC][1]-$lGe_); |
| 487 | PPW(1,$RC,0.0,-$lGe_); |
| 488 | LPP(3,$RP2,1); |
| 489 | PLL($RP3,$LMih,3); |
| 490 | $l1_rt=AD($RF,$RM); |
| 491 | PZP($RP4,$RF,$RM,($bSuM+$l1_rt)/$l1_rt*100); |
| 492 | $bSuv_rt=(10*$lSu_+3*(10*$uBr_-920)/40-1+10*$zBr/10)/10; |
| 493 | $bSA_rt=(10*$uBr_/80+12.5)/10; |
| 494 | $l2_rt=$bSuv_rt/2+$bSA_rt/2; |
| 495 | PZP($RP5,$RF,$RM,$l2_rt/$l1_rt*100); |
| 496 | PW($RP6,$P_[$RG][0]-$bRue/2+1.0,0.0); |
| 497 | LPP(4,$RP6,$RP5); |
| 498 | PZP($RP7,$RP5,$RP6,9.0/AD($RP5,$RP6)*100); |
| 499 | KR(1,$RF,$bSuv_rt); |
| 500 | KR(2,$RP7,AD($RP7,$RP4)); |
| 501 | PKK(1,1,2,1);PKK(2,1,2,2); |
| 502 | if($P_[1][0]<$P_[2][0])PPW($RP8,1,0.0,0.0);else PPW($RP8,2,0.0,0.0); |
| 503 | PZP($RP9I,$RF,$RP8,($bSuv_rt/2)/AD($RF,$RP8)*100); |
| 504 | KR(1,$RP4,$bSuv_rt/2+$bSuM); |
| 505 | KR(2,$RP7,AD($RP7,$RP9I)); |
| 506 | PKK(1,1,2,1);PKK(2,1,2,2); |
| 507 | if($P_[1][1]>$P_[2][1])PPW($RP9A,1,0.0,0.0);else PPW($RP9A,2,0.0,0.0); |
| 508 | PPW($RP10,$RG,-$bRue/2-1.0,0.0); |
| 509 | LPDP(5,$RP10,$LMih); |
| 510 | PLL($RP11,3,5); |
| 511 | $l1_rt=AD($RP10,$RP11); |
| 512 | PZP($RP12,$RP10,$RP11,2.0/$l1_rt*100); |
| 513 | PZP($RP13,$RP11,$RP10,($lGe_/4)/$l1_rt*100); |
| 514 | LPDP(6,$RK,$LMih); |
| 515 | LPP(7,$RC,$RP1); |
| 516 | PLL($RP14,6,7); |
| 517 | PLL($RP15,6,3); |
| 518 | PPW($RP16,$RP14,1.0,0.0); |
| 519 | PPW($RP17,$RP15,-1.0,0.0); |
| 520 | LSP(8,$RP16,$LMih); |
| 521 | PLL($RP18,5,8); |
| 522 | PLL($RP19,8,$LMih); |
| 523 | KR(1,$RP18,$bTAh/2); |
| 524 | PKL(1,1,8,1);PKL(2,1,8,2); |
| 525 | if($P_[1][0]>$P_[2][0]){PPW($RP18I,1,0,0);PPW($RP18A,2,0,0);} |
| 526 | else {PPW($RP18A,1,0,0);PPW($RP18I,2,0,0);} |
| 527 | LPA(9,-abs($hSaT),8); |
| 528 | PLL($RP20,$LMih,9); |
| 529 | LPP(10,$RP13,$RP18I); |
| 530 | LPP(11,$RP13,$RP18A); |
| 531 | LPP(12,$RP16,$RP17); |
| 532 | PLL($RP21,9,10); |
| 533 | PLL($RP22,9,11); |
| 534 | PLL($RP23,9,12); |
| 535 | $l1_rt=AD($RB,$RG); |
| 536 | PZP($RP24,$RB,$RG,$VHth/$l1_rt*100); |
| 537 | $l1_rt=AD($RF,$RP9I); |
| 538 | PZP($RP25,$RF,$RP9I,$VHh/$l1_rt*100); |
| 539 | |
| 540 | $rt_contour=[ |
| 541 | ['nr'=>$RP20,'lbl'=>'P20','info'=>''], |
| 542 | ['nr'=>$RP21,'lbl'=>'P21','info'=>'K'], |
| 543 | ['nr'=>$RP18I,'lbl'=>'P18i','info'=>''], |
| 544 | ['nr'=>$RP12,'lbl'=>'P12','info'=>''], |
| 545 | ['nr'=>$RP18A,'lbl'=>'P18a','info'=>''], |
| 546 | ['nr'=>$RP22,'lbl'=>'P22','info'=>'K'], |
| 547 | ['nr'=>$RP23,'lbl'=>'P23','info'=>''], |
| 548 | ['nr'=>$RP16,'lbl'=>'P16','info'=>'K'], |
| 549 | ['nr'=>$RK, 'lbl'=>'K', 'info'=>'A'], |
| 550 | ['nr'=>$RL, 'lbl'=>'L', 'info'=>'X'], |
| 551 | ['nr'=>$RN, 'lbl'=>'N', 'info'=>'X'], |
| 552 | ['nr'=>$RP4, 'lbl'=>'P4', 'info'=>'E'], |
| 553 | ['nr'=>$RP9A,'lbl'=>'P9a','info'=>'K'], |
| 554 | ['nr'=>$RP7, 'lbl'=>'P7', 'info'=>''], |
| 555 | ['nr'=>$RP9I,'lbl'=>'P9i','info'=>''], |
| 556 | ['nr'=>$RP25,'lbl'=>'P25','info'=>'A'], |
| 557 | ['nr'=>$RP24,'lbl'=>'P24','info'=>'E'], |
| 558 | ['nr'=>$RP20,'lbl'=>'','info'=>''], |
| 559 | ]; |
| 560 | $rt_internal=[[$RD,$RC],[$RA,$RB],[$RH,$RG]]; |
| 561 | |
| 562 | // ============================================================ |
| 563 | // SVG-Hilfsfunktionen |
| 564 | // ============================================================ |
| 565 | |
| 566 | // ============================================================ |
| 567 | // Bounding-Box + Skalierung |
| 568 | // ============================================================ |
| 569 | $vtXs=[];$vtYs=[]; |
| 570 | foreach($vt_contour as $cp){$nr=$cp['nr'];if($P_[$nr][0]<1e14){$vtXs[]=$P_[$nr][0];$vtYs[]=$P_[$nr][1];}} |
| 571 | $rtXs=[];$rtYs=[]; |
| 572 | foreach($rt_contour as $cp){$nr=$cp['nr'];if($P_[$nr][0]<1e14){$rtXs[]=$P_[$nr][0];$rtYs[]=$P_[$nr][1];}} |
| 573 | |
| 574 | $vtMinX=min($vtXs);$vtMaxX=max($vtXs); |
| 575 | $rtMinX=min($rtXs);$rtMaxX=max($rtXs); |
| 576 | $comMinY=min(min($vtYs),min($rtYs)); |
| 577 | $comMaxY=max(max($vtYs),max($rtYs)); |
| 578 | |
| 579 | $margin=44; $gap=36; $SVG_H=680; |
| 580 | $scale=($SVG_H-2*$margin)/($comMaxY-$comMinY+0.001); |
| 581 | $vtW=($vtMaxX-$vtMinX)*$scale; |
| 582 | $rtW=($rtMaxX-$rtMinX)*$scale; |
| 583 | $SVG_W=(int)ceil($margin+$vtW+$gap+$rtW+$margin+8); |
| 584 | $vtOx=$margin-$vtMinX*$scale; |
| 585 | $rtOx=$margin+$vtW+$gap-$rtMinX*$scale; |
| 586 | $Oy =$SVG_H-$margin+$comMinY*$scale; |
| 587 | |
| 588 | // ============================================================ |
| 589 | // SVG-Hilfsfunktionen |
| 590 | // ============================================================ |
| 591 | function toSvgXY($nr,&$P,$sc,$ox,$oy){ |
| 592 | return [round($P[$nr][0]*$sc+$ox,2), round(-$P[$nr][1]*$sc+$oy,2)]; |
| 593 | } |
| 594 | function catmullRomPath(array $pts):string{ |
| 595 | $n=count($pts); |
| 596 | if($n<2)return ''; |
| 597 | if($n==2)return "M {$pts[0][0]},{$pts[0][1]} L {$pts[1][0]},{$pts[1][1]}"; |
| 598 | $p=array_merge([$pts[0]],$pts,[$pts[$n-1]]); |
| 599 | $d="M {$p[1][0]},{$p[1][1]}"; |
| 600 | for($i=1;$i<count($p)-2;$i++){ |
| 601 | $p0=$p[$i-1];$p1=$p[$i];$p2=$p[$i+1];$p3=$p[$i+2]; |
| 602 | $cp1x=round($p1[0]+($p2[0]-$p0[0])/6,2); |
| 603 | $cp1y=round($p1[1]+($p2[1]-$p0[1])/6,2); |
| 604 | $cp2x=round($p2[0]-($p3[0]-$p1[0])/6,2); |
| 605 | $cp2y=round($p2[1]-($p3[1]-$p1[1])/6,2); |
| 606 | $d.=" C $cp1x,$cp1y $cp2x,$cp2y {$p2[0]},{$p2[1]}"; |
| 607 | } |
| 608 | return $d; |
| 609 | } |
| 610 | function buildPath(array $contour,array &$P,$sc,$ox,$oy, |
| 611 | string $teil='', array $CURVES=[], array $PNAMES=[]): string { |
| 612 | $path='';$inCurve=false;$curveStart=null;$curvePts=[];$curveNrs=[]; |
| 613 | foreach($contour as $cp){ |
| 614 | $nr=$cp['nr']; |
| 615 | if($P[$nr][0]>1e14)continue; |
| 616 | $sx=round($P[$nr][0]*$sc+$ox,2); |
| 617 | $sy=round(-$P[$nr][1]*$sc+$oy,2); |
| 618 | |
| 619 | if(!$inCurve && $cp['info']==='A'){ |
| 620 | if($path==='')$path="M $sx,$sy"; else $path.=" L $sx,$sy"; |
| 621 | $inCurve=true; |
| 622 | $curveStart=['sx'=>$sx,'sy'=>$sy,'nr'=>$nr]; |
| 623 | $curvePts=[[$sx,$sy]]; $curveNrs=[$nr]; |
| 624 | }elseif($inCurve){ |
| 625 | $curvePts[]=[$sx,$sy]; $curveNrs[]=$nr; |
| 626 | if($cp['info']==='E'||$cp['info']==='X'){ |
| 627 | // Echte Datenbankwerte suchen |
| 628 | $startNr=$curveNrs[0]; $endNr=$nr; |
| 629 | $ckey=$teil.'_'.$startNr.'_'.$endNr; |
| 630 | $p1=['', $curveStart['sx'], $curveStart['sy']]; |
| 631 | $p2=['', $sx, $sy]; |
| 632 | |
| 633 | // Kurven-Key aus Mapping: Teil_StartNr_EndNr |
| 634 | global $CURVE_MAP, $CURVES; |
| 635 | $mapkey = $teil.'_'.$startNr.'_'.$endNr; |
| 636 | $dbkey = $CURVE_MAP[$mapkey] ?? null; |
| 637 | if($dbkey && !empty($CURVES[$dbkey]) && count($CURVES[$dbkey]['pts'])>=2){ |
| 638 | // Echte Kurve aus Datenbank → einhängen |
| 639 | $path.=' '.applyCurve( |
| 640 | [$curveStart['sx'],$curveStart['sy']], |
| 641 | [$sx,$sy], |
| 642 | $CURVES[$dbkey]['pts'], $sc); |
| 643 | } else { |
| 644 | // Fallback: Catmull-Rom mit vorhandenen Stützpunkten |
| 645 | $seg=catmullRomPath($curvePts); |
| 646 | $cOnly=preg_replace('/^M[\s\d.,\-]+\s*/','', $seg); |
| 647 | $path.=' '.$cOnly; |
| 648 | } |
| 649 | if($cp['info']==='E'){$inCurve=false;} |
| 650 | else{ |
| 651 | $curveStart=['sx'=>$sx,'sy'=>$sy,'nr'=>$nr]; |
| 652 | $curvePts=[[$sx,$sy]]; $curveNrs=[$nr]; |
| 653 | } |
| 654 | } |
| 655 | }else{ |
| 656 | if($path==='')$path="M $sx,$sy"; else $path.=" L $sx,$sy"; |
| 657 | } |
| 658 | } |
| 659 | return $path.' Z'; |
| 660 | } |
| 661 | function svgIL($p1,$p2,&$P,$sc,$ox,$oy,$col,$dash='5,3'){ |
| 662 | if($P[$p1][0]>1e14||$P[$p2][0]>1e14)return ''; |
| 663 | [$x1,$y1]=[round($P[$p1][0]*$sc+$ox,1),round(-$P[$p1][1]*$sc+$oy,1)]; |
| 664 | [$x2,$y2]=[round($P[$p2][0]*$sc+$ox,1),round(-$P[$p2][1]*$sc+$oy,1)]; |
| 665 | return "<line x1='$x1' y1='$y1' x2='$x2' y2='$y2' stroke='$col' stroke-width='1' stroke-dasharray='$dash'/>\n"; |
| 666 | } |
| 667 | function svgLbl($nr,$lbl,&$P,$sc,$ox,$oy,$col){ |
| 668 | if($P[$nr][0]>1e14||!$lbl)return ''; |
| 669 | [$x,$y]=[round($P[$nr][0]*$sc+$ox,1),round(-$P[$nr][1]*$sc+$oy,1)]; |
| 670 | return "<circle cx='$x' cy='$y' r='2.5' fill='$col' opacity='.75'/>\n" |
| 671 | ."<text x='".($x+4)."' y='".($y-3)."' font-size='8' fill='$col'" |
| 672 | ." font-family='DM Mono,monospace'" |
| 673 | ." paint-order='stroke' stroke='#fffef9' stroke-width='2.5'>$lbl</text>\n"; |
| 674 | } |
| 675 | |
| 676 | // ============================================================ |
| 677 | // Linie klippen auf sichtbaren SVG-Bereich |
| 678 | // Gibt [x1,y1,x2,y2] in SVG-Koordinaten zurück oder null |
| 679 | // ============================================================ |
| 680 | function clipLine($A,$B,$C,$ox,$oy,$W,$H){ |
| 681 | // Ax+By+C=0 (Model-Koordinaten) |
| 682 | // Wir berechnen Schnittpunkte mit den 4 Rändern im Modellraum |
| 683 | $sc = $GLOBALS['scale']; |
| 684 | // Modellbereich: x von (0-ox)/sc bis (W-ox)/sc, y von (oy-H)/sc bis oy/sc |
| 685 | $xmin=(0-$ox)/$sc; $xmax=($W-$ox)/$sc; |
| 686 | $ymin=($oy-$H)/$sc; $ymax=$oy/$sc; |
| 687 | $pts=[]; |
| 688 | if(abs($B)>1e-9){ |
| 689 | // x=xmin → y |
| 690 | $y=(-$C-$A*$xmin)/$B; if($y>=$ymin&&$y<=$ymax) $pts[]=[$xmin,$y]; |
| 691 | // x=xmax → y |
| 692 | $y=(-$C-$A*$xmax)/$B; if($y>=$ymin&&$y<=$ymax) $pts[]=[$xmax,$y]; |
| 693 | } |
| 694 | if(abs($A)>1e-9){ |
| 695 | // y=ymin → x |
| 696 | $x=(-$C-$B*$ymin)/$A; if($x>=$xmin&&$x<=$xmax) $pts[]=[$x,$ymin]; |
| 697 | // y=ymax → x |
| 698 | $x=(-$C-$B*$ymax)/$A; if($x>=$xmin&&$x<=$xmax) $pts[]=[$x,$ymax]; |
| 699 | } |
| 700 | if(count($pts)<2) return null; |
| 701 | // SVG-Koordinaten |
| 702 | $x1=round($pts[0][0]*$sc+$ox,1); $y1=round(-$pts[0][1]*$sc+$oy,1); |
| 703 | $x2=round($pts[1][0]*$sc+$ox,1); $y2=round(-$pts[1][1]*$sc+$oy,1); |
| 704 | return [$x1,$y1,$x2,$y2]; |
| 705 | } |
| 706 | |
| 707 | // ============================================================ |
| 708 | // Punkt-Namen-Map (für Label im Player) |
| 709 | // ============================================================ |
| 710 | $PNAMES = [ |
| 711 | 100=>'A',101=>'B',102=>'C',103=>'D',104=>'E',105=>'F',106=>'G',107=>'H', |
| 712 | 108=>'I',109=>'J',110=>'K',111=>'L',112=>'M',113=>'N',114=>'O', |
| 713 | 115=>'P1',116=>'P10',117=>'P11',118=>'P11a',119=>'P11i',120=>'P12', |
| 714 | 121=>'P13',122=>'P14',123=>'P15',124=>'P16',125=>'P17',126=>'P18', |
| 715 | 127=>'P19',128=>'P2',129=>'P20',130=>'P21',131=>'P22',132=>'P23', |
| 716 | 133=>'P3',134=>'P4',135=>'P5',136=>'P6',137=>'P7',138=>'P9',139=>'Ppkt', |
| 717 | 200=>'A',201=>'B',202=>'C',203=>'D',204=>'E',205=>'F',206=>'G',207=>'H', |
| 718 | 208=>'I',209=>'J',210=>'K',211=>'L',212=>'M',213=>'N',214=>'O', |
| 719 | 215=>'P1',216=>'P10',217=>'P11',218=>'P12',219=>'P13',220=>'P14', |
| 720 | 221=>'P15',222=>'P16',223=>'P17',224=>'P18',225=>'P18a',226=>'P18i', |
| 721 | 227=>'P19',228=>'P2',229=>'P20',230=>'P21',231=>'P22',232=>'P23', |
| 722 | 233=>'P24',234=>'P25',235=>'P3',236=>'P4',237=>'P5',238=>'P6', |
| 723 | 239=>'P7',240=>'P8',241=>'P9a',242=>'P9i', |
| 724 | ]; |
| 725 | |
| 726 | // ============================================================ |
| 727 | // SVG-Elemente je Step aufbauen |
| 728 | // Jeder Step bekommt genau die NEU hinzugekommenen Elemente |
| 729 | // ============================================================ |
| 730 | // Wir vergleichen aufeinanderfolgende Snapshots |
| 731 | $svgSteps = []; // ['type'=>'P'|'L'|'K', 'svg'=>'...', 'desc'=>'...', 'fn'=>'...'] |
| 732 | $prevP=[]; $prevL=[]; $prevK=[]; |
| 733 | |
| 734 | foreach($steps as $si=>$step){ |
| 735 | $curP=$step['P']; $curL=$step['L']; $curK=$step['K']; |
| 736 | $part=$step['part']; |
| 737 | $ox=($part==='RT')?$rtOx:$vtOx; |
| 738 | $col=($part==='RT')?'#8a3a3a':'#3a5a8a'; |
| 739 | $hlcol=($part==='RT')?'#cc3333':'#1155cc'; |
| 740 | $newSvg=''; |
| 741 | |
| 742 | // --- Neue Punkte --- |
| 743 | foreach($curP as $nr=>$p){ |
| 744 | if(isset($prevP[$nr])) continue; |
| 745 | $sx=round($p[0]*$scale+$ox,2); |
| 746 | $sy=round(-$p[1]*$scale+$Oy,2); |
| 747 | $lbl=$PNAMES[$nr]??('P'.$nr); |
| 748 | // Kleiner Punkt + Label |
| 749 | $newSvg.="<circle cx='$sx' cy='$sy' r='2.8' fill='$col'/>"; |
| 750 | $newSvg.="<text x='".($sx+4)."' y='".($sy-3)."' font-size='8' fill='$col'" |
| 751 | ." font-family='DM Mono,monospace'" |
| 752 | ." paint-order='stroke' stroke='#fffef9' stroke-width='2.5'>$lbl</text>"; |
| 753 | } |
| 754 | |
| 755 | // --- Neue Linien --- |
| 756 | foreach($curL as $nr=>$l){ |
| 757 | if(isset($prevL[$nr])) continue; |
| 758 | $seg=clipLine($l[0],$l[1],$l[2],$ox,$Oy,$SVG_W,$SVG_H); |
| 759 | if($seg){ |
| 760 | [$x1,$y1,$x2,$y2]=$seg; |
| 761 | $newSvg.="<line x1='$x1' y1='$y1' x2='$x2' y2='$y2'" |
| 762 | ." stroke='#c8960a' stroke-width='1.2' stroke-dasharray='6,3' opacity='.85'/>"; |
| 763 | // Linienbezeichnung in der Mitte |
| 764 | $mx=round(($x1+$x2)/2,1); $my=round(($y1+$y2)/2-6,1); |
| 765 | $newSvg.="<text x='$mx' y='$my' font-size='8.5' fill='#a07800'" |
| 766 | ." font-family='DM Mono,monospace' text-anchor='middle'" |
| 767 | ." paint-order='stroke' stroke='#fffef9' stroke-width='2'>L$nr</text>"; |
| 768 | } |
| 769 | } |
| 770 | |
| 771 | // --- Neue Kreise --- |
| 772 | foreach($curK as $nr=>$k){ |
| 773 | if(isset($prevK[$nr])) continue; |
| 774 | $kpart=$k[3]??$part; |
| 775 | $kox=($kpart==='RT')?$rtOx:$vtOx; |
| 776 | $cx=round($k[0]*$scale+$kox,2); |
| 777 | $cy=round(-$k[1]*$scale+$Oy,2); |
| 778 | $cr=round($k[2]*$scale,2); |
| 779 | $newSvg.="<circle cx='$cx' cy='$cy' r='$cr' fill='none'" |
| 780 | ." stroke='#2090c0' stroke-width='1.2' stroke-dasharray='5,3' opacity='.8'/>"; |
| 781 | $newSvg.="<text x='".($cx+$cr+3)."' y='$cy' font-size='8.5' fill='#2070a0'" |
| 782 | ." font-family='DM Mono,monospace'" |
| 783 | ." paint-order='stroke' stroke='#fffef9' stroke-width='2'>K$nr</text>"; |
| 784 | } |
| 785 | |
| 786 | $svgSteps[]=[ |
| 787 | 'fn' =>$step['fn'], |
| 788 | 'desc'=>$step['desc'], |
| 789 | 'part'=>$part, |
| 790 | 'svg' =>$newSvg, |
| 791 | ]; |
| 792 | |
| 793 | $prevP=$curP; $prevL=$curL; $prevK=$curK; |
| 794 | } |
| 795 | |
| 796 | |
| 797 | // Mapping: Teil_StartNr_EndNr → Kurven-Schlüssel |
| 798 | $CURVE_MAP = [ |
| 799 | 'VT_136_138' => 'VT_Halsloch_vorn_unten', |
| 800 | 'VT_138_137' => 'VT_Halsloch_vorn_oben', |
| 801 | 'VT_116_109' => 'VT_Armloch_vorn_oben', |
| 802 | 'VT_109_110' => 'VT_Armloch_vorn_unten', |
| 803 | 'RT_210_211' => 'RT_Armloch_vorn_unten', |
| 804 | 'RT_211_213' => 'RT_Armloch_vorn_Mitte', |
| 805 | 'RT_213_236' => 'RT_Armloch_vorn_oben', |
| 806 | 'RT_234_233' => 'RT_Halsloch_vorn_unten', |
| 807 | ]; |
| 808 | |
| 809 | // Label-Maps für Kurven-Lookup (Punktnummer → Name) |
| 810 | |
| 811 | $CURVES = [ |
| 812 | // Halslochkurve, vorn unten |
| 813 | 'VT_Halsloch_vorn_unten' => [ |
| 814 | 'teil'=>'VT', 'loc1'=>'Halsloch', 'loc2'=>'vorn,unten', |
| 815 | 'n'=>5, |
| 816 | 'pts'=>[[0.0,0.0], [1.6,0.2], [3.1,0.7], [4.3,1.4], [5.4,2.3]], |
| 817 | 'spl'=>[[0.0,0.0], [0.805,0.061], [1.6,0.2], [2.362,0.413], [3.1,0.7], [3.716,1.021], [4.3,1.4], [4.868,1.829], [5.4,2.3]], |
| 818 | ], |
| 819 | |
| 820 | // Halslochkurve, vorn oben |
| 821 | 'VT_Halsloch_vorn_oben' => [ |
| 822 | 'teil'=>'VT', 'loc1'=>'Halsloch', 'loc2'=>'vorn,oben', |
| 823 | 'n'=>5, |
| 824 | 'pts'=>[[0.0,0.0], [1.2,1.7], [1.9,3.3], [2.4,5.0], [2.6,6.6]], |
| 825 | 'spl'=>[[0.0,0.0], [0.639,0.822], [1.2,1.7], [1.584,2.484], [1.9,3.3], [2.18,4.141], [2.4,5.0], [2.534,5.796], [2.6,6.6]], |
| 826 | ], |
| 827 | |
| 828 | // Armlochkurve vorne oben |
| 829 | 'VT_Armloch_vorn_oben' => [ |
| 830 | 'teil'=>'VT', 'loc1'=>'Armloch', 'loc2'=>'vorn,oben', |
| 831 | 'n'=>7, |
| 832 | 'pts'=>[[0.0,0.0], [-1.6,-1.9], [-3.1,-3.9], [-4.6,-6.3], [-5.6,-8.6], [-6.3,-11.3], [-6.5,-14.2]], |
| 833 | 'spl'=>[[0.0,0.0], [-0.813,-0.939], [-1.6,-1.9], [-2.368,-2.886], [-3.1,-3.9], [-3.883,-5.08], [-4.6,-6.3], [-5.146,-7.43], [-5.6,-8.6], [-6.007,-9.936], [-6.3,-11.3], [-6.466,-12.745], [-6.5,-14.2]], |
| 834 | ], |
| 835 | |
| 836 | // Armlochkurve vorne unten |
| 837 | 'VT_Armloch_vorn_unten' => [ |
| 838 | 'teil'=>'VT', 'loc1'=>'Armloch', 'loc2'=>'vorn,unten', |
| 839 | 'n'=>5, |
| 840 | 'pts'=>[[0.0,0.0], [0.5,-2.1], [1.5,-3.6], [2.9,-4.7], [5.3,-5.3]], |
| 841 | 'spl'=>[[0.0,0.0], [0.166,-1.07], [0.5,-2.1], [0.934,-2.892], [1.5,-3.6], [2.148,-4.223], [2.9,-4.7], [4.074,-5.105], [5.3,-5.3]], |
| 842 | ], |
| 843 | |
| 844 | // Armloch vorne unten |
| 845 | 'RT_Armloch_vorn_unten' => [ |
| 846 | 'teil'=>'RT', 'loc1'=>'Armloch', 'loc2'=>'vorn,unten', |
| 847 | 'n'=>5, |
| 848 | 'pts'=>[[0.0,0.0], [2.4,0.4], [4.4,1.4], [5.9,2.8], [6.8,4.7]], |
| 849 | 'spl'=>[[0.0,0.0], [1.214,0.115], [2.4,0.4], [3.436,0.827], [4.4,1.4], [5.211,2.038], [5.9,2.8], [6.438,3.708], [6.8,4.7]], |
| 850 | ], |
| 851 | |
| 852 | // Armloch vorn Mitte |
| 853 | 'RT_Armloch_vorn_Mitte' => [ |
| 854 | 'teil'=>'RT', 'loc1'=>'Armloch', 'loc2'=>'vorn,Mitte', |
| 855 | 'n'=>4, |
| 856 | 'pts'=>[[0.0,0.0], [0.4,1.7], [0.5,3.1], [0.6,4.9]], |
| 857 | 'spl'=>[[0.0,0.0], [0.23,0.843], [0.4,1.7], [0.467,2.398], [0.5,3.1], [0.6,4.9]], |
| 858 | ], |
| 859 | |
| 860 | // Armloch R³ckenteil vorn oben |
| 861 | 'RT_Armloch_vorn_oben' => [ |
| 862 | 'teil'=>'RT', 'loc1'=>'Armloch', 'loc2'=>'vorn,oben', |
| 863 | 'n'=>6, |
| 864 | 'pts'=>[[0.0,0.0], [-0.4,2.4], [-1.0,4.5], [-1.8,6.8], [-2.9,8.9], [-4.0,11.2]], |
| 865 | 'spl'=>[[0.0,0.0], [-0.168,1.205], [-0.4,2.4], [-0.676,3.456], [-1.0,4.5], [-1.372,5.661], [-1.8,6.8], [-2.335,7.857], [-2.9,8.9], [-4.0,11.2]], |
| 866 | ], |
| 867 | |
| 868 | // R³ckenteil Halsloch vorn unten |
| 869 | 'RT_Halsloch_vorn_unten' => [ |
| 870 | 'teil'=>'RT', 'loc1'=>'Halsloch', 'loc2'=>'vorn,unten', |
| 871 | 'n'=>6, |
| 872 | 'pts'=>[[0.0,0.0], [1.4,-1.5], [3.2,-2.4], [5.2,-2.7], [6.9,-2.8], [8.8,-2.8]], |
| 873 | 'spl'=>[[0.0,0.0], [0.634,-0.811], [1.4,-1.5], [2.261,-2.025], [3.2,-2.4], [4.19,-2.599], [5.2,-2.7], [6.049,-2.767], [6.9,-2.8], [7.85,-2.813], [8.8,-2.8]], |
| 874 | ], |
| 875 | |
| 876 | ]; |
| 877 | |
| 878 | $vt_lbl_map=[ |
| 879 | $A=>'A',$B=>'B',$C=>'C',$D=>'D',$E=>'E',$F=>'F',$G=>'G', |
| 880 | $H=>'H',$I=>'I',$J=>'J',$K=>'K',$L=>'L',$M=>'M',$N=>'N', |
| 881 | $P4=>'P4',$P5=>'P5',$P6=>'P6',$P7=>'P7', |
| 882 | $P10=>'P10',$P16=>'P16',$P19=>'P19', |
| 883 | $P11a=>'P11a',$P11i=>'P11i']; |
| 884 | $rt_lbl_map=[ |
| 885 | $RA=>'A',$RB=>'B',$RC=>'C',$RD=>'D',$RE=>'E',$RF=>'F',$RG=>'G', |
| 886 | $RH=>'H',$RI=>'I',$RJ=>'J',$RK=>'K',$RL=>'L',$RM=>'M',$RN=>'N', |
| 887 | $RP4=>'P4',$RP7=>'P7',$RP9A=>'P9a',$RP9I=>'P9i', |
| 888 | $RP16=>'P16',$RP20=>'P20',$RP24=>'P24',$RP25=>'P25']; |
| 889 | |
| 890 | // Konturpfade mit echten Kurven aus Datenbank |
| 891 | $vtPath=buildPath($vt_contour,$P_,$scale,$vtOx,$Oy,'VT',$CURVES,$vt_lbl_map); |
| 892 | $rtPath=buildPath($rt_contour,$P_,$scale,$rtOx,$Oy,'RT',$CURVES,$rt_lbl_map); |
| 893 | |
| 894 | |
| 895 | |
| 896 | |
| 897 | // Kurven nach Teil+Punktnummern indexieren |
| 898 | // (wird nach Konstruktion befüllt, hier Vorinitialisierung) |
| 899 | $CURVE_IDX = []; |
| 900 | foreach ($CURVES as $key => $cv) { |
| 901 | $CURVE_IDX[$cv['teil'].'_'.$cv['loc1'].'_'.$cv['loc2']] = $key; |
| 902 | } |
| 903 | |
| 904 | // ================================================================ |
| 905 | // AYSPLINE – Kubischer Hermite-Spline (klassisch) |
| 906 | // PHP-Implementierung für AutoCons |
| 907 | // ================================================================ |
| 908 | |
| 909 | function ayspline_norm(float $a, float $b): float { |
| 910 | return sqrt($a*$a + $b*$b); |
| 911 | } |
| 912 | |
| 913 | function ayspline_rtb(array $PK, int $N): ?array { |
| 914 | $EK = array_fill(0, $N, [0.0, 0.0]); |
| 915 | for ($i=1; $i<=$N-2; $i++) { |
| 916 | $EK[$i][0] = $PK[$i+1][0] - $PK[$i-1][0]; |
| 917 | $EK[$i][1] = $PK[$i+1][1] - $PK[$i-1][1]; |
| 918 | } |
| 919 | $a=$PK[1][0]-$PK[0][0]; $b=$PK[1][1]-$PK[0][1]; |
| 920 | $be=ayspline_norm($a,$b); if($be<1e-10) return null; |
| 921 | $EK[0][0]=$a/$be; $EK[0][1]=$b/$be; |
| 922 | $skal=($PK[2][0]-$PK[1][0])*$EK[0][0]+($PK[2][1]-$PK[1][1])*$EK[0][1]; |
| 923 | $EK[0][0]=2*$skal*$EK[0][0]-$PK[2][0]+$PK[1][0]+$a; |
| 924 | $EK[0][1]=2*$skal*$EK[0][1]-$PK[2][1]+$PK[1][1]+$b; |
| 925 | $a=$PK[$N-1][0]-$PK[$N-2][0]; $b=$PK[$N-1][1]-$PK[$N-2][1]; |
| 926 | $be=ayspline_norm($a,$b); if($be<1e-10) return null; |
| 927 | $EK[$N-1][0]=$a/$be; $EK[$N-1][1]=$b/$be; |
| 928 | $skal=($PK[$N-3][0]-$PK[$N-2][0])*$EK[$N-1][0] |
| 929 | +($PK[$N-3][1]-$PK[$N-2][1])*$EK[$N-1][1]; |
| 930 | $EK[$N-1][0]=$a-2*$skal*$EK[$N-1][0]+$PK[$N-3][0]-$PK[$N-2][0]; |
| 931 | $EK[$N-1][1]=$b-2*$skal*$EK[$N-1][1]+$PK[$N-3][1]-$PK[$N-2][1]; |
| 932 | for ($j=0; $j<$N; $j++) { |
| 933 | $be=ayspline_norm($EK[$j][0],$EK[$j][1]); |
| 934 | if($be<1e-10) return null; |
| 935 | $EK[$j][0]/=$be; $EK[$j][1]/=$be; |
| 936 | } |
| 937 | return $EK; |
| 938 | } |
| 939 | |
| 940 | function ayspline(array $PK, float $eps=0.4): array { |
| 941 | $N=count($PK); |
| 942 | if($N<2) return $PK; |
| 943 | if($N==2) { |
| 944 | $out=[]; |
| 945 | for($t=0;$t<=8;$t++){$f=$t/8; |
| 946 | $out[]=[$PK[0][0]+$f*($PK[1][0]-$PK[0][0]), |
| 947 | $PK[0][1]+$f*($PK[1][1]-$PK[0][1])];} |
| 948 | return $out; |
| 949 | } |
| 950 | $EK=ayspline_rtb($PK,$N); |
| 951 | if($EK===null) return $PK; |
| 952 | $XP=[]; |
| 953 | for($i=0;$i<$N-1;$i++){ |
| 954 | $dx=$PK[$i+1][0]-$PK[$i][0]; $dy=$PK[$i+1][1]-$PK[$i][1]; |
| 955 | $S=sqrt($dx*$dx+$dy*$dy); if($S<1e-10) continue; |
| 956 | $A=[[$PK[$i][0],$PK[$i][1]],[$EK[$i][0]*$S,$EK[$i][1]*$S], |
| 957 | [$PK[$i+1][0],$PK[$i+1][1]],[$EK[$i+1][0]*$S,$EK[$i+1][1]*$S]]; |
| 958 | $nSub=max(4,(int)($S/$eps)+1); |
| 959 | for($k=0;$k<$nSub;$k++){ |
| 960 | $f=$k/$nSub; $f2=$f*$f; $f3=$f2*$f; |
| 961 | $u0=1-3*$f2+2*$f3; $u1=$f-2*$f2+$f3; |
| 962 | $u2=3*$f2-2*$f3; $u3=$f3-$f2; |
| 963 | $XP[]=[round($u0*$A[0][0]+$u1*$A[1][0]+$u2*$A[2][0]+$u3*$A[3][0],3), |
| 964 | round($u0*$A[0][1]+$u1*$A[1][1]+$u2*$A[2][1]+$u3*$A[3][1],3)]; |
| 965 | } |
| 966 | } |
| 967 | $XP[]=$PK[$N-1]; |
| 968 | return $XP; |
| 969 | } |
| 970 | |
| 971 | // Kurve aus DB auf Kontur-Segment anwenden (p1,p2 in SVG-Koordinaten) |
| 972 | function applyCurve(array $p1, array $p2, array $rawPts, float $sc): string { |
| 973 | $n=count($rawPts); |
| 974 | if($n<2) return "L {$p2[0]},{$p2[1]}"; |
| 975 | |
| 976 | // Rohdaten: cm-Koordinaten relativ zum Startpunkt |
| 977 | // Letzter Punkt = Endpunkt der Kurve |
| 978 | $endX=$rawPts[$n-1][0]; $endY=$rawPts[$n-1][1]; |
| 979 | $rawLen=sqrt($endX*$endX+$endY*$endY); |
| 980 | if($rawLen<1e-10) return "L {$p2[0]},{$p2[1]}"; |
| 981 | |
| 982 | // Ziel: p1→p2 in SVG-Koordinaten |
| 983 | $tgtDX=$p2[0]-$p1[0]; $tgtDY=$p2[1]-$p1[1]; |
| 984 | $tgtLen=sqrt($tgtDX*$tgtDX+$tgtDY*$tgtDY); |
| 985 | if($tgtLen<1e-10) return "L {$p2[0]},{$p2[1]}"; |
| 986 | |
| 987 | // Rotationsmatrix: raw-Koordinatensystem → SVG-Koordinatensystem |
| 988 | // raw: x nach rechts, y nach oben (Modell) |
| 989 | // SVG: x nach rechts, y nach unten → y invertieren |
| 990 | $rawEndX=$endX; $rawEndY=-$endY; // y invertieren |
| 991 | $rawEndLen=sqrt($rawEndX*$rawEndX+$rawEndY*$rawEndY); |
| 992 | |
| 993 | $cos_a=($tgtDX*$rawEndX+$tgtDY*$rawEndY)/($tgtLen*$rawEndLen); |
| 994 | $sin_a=($tgtDY*$rawEndX-$tgtDX*$rawEndY)/($tgtLen*$rawEndLen); |
| 995 | $scale=$tgtLen/$rawEndLen; |
| 996 | |
| 997 | // Alle Stützpunkte transformieren |
| 998 | $pts=[]; |
| 999 | foreach($rawPts as $rp){ |
| 1000 | $rx=$rp[0]; $ry=-$rp[1]; // y invertieren |
| 1001 | $tx=($cos_a*$rx-$sin_a*$ry)*$scale; |
| 1002 | $ty=($sin_a*$rx+$cos_a*$ry)*$scale; |
| 1003 | $pts[]=[round($p1[0]+$tx,2), round($p1[1]+$ty,2)]; |
| 1004 | } |
| 1005 | |
| 1006 | // Hermite-Spline interpolieren |
| 1007 | $splined=ayspline($pts, 0.5); |
| 1008 | |
| 1009 | // SVG-Pfad erzeugen |
| 1010 | $path=''; |
| 1011 | foreach($splined as $sp) |
| 1012 | $path.=" L {$sp[0]},{$sp[1]}"; |
| 1013 | return ltrim($path); |
| 1014 | } |
| 1015 | |
| 1016 | /** |
| 1017 | * AutoCons – Kurvendaten aus curves.dbf (Optikon-Oberteil) |
| 1018 | * Originalwerte: Georg Müller, ASSYST/CONEX ca. 1992-1996 |
| 1019 | * Jede Kurve: Stützpunkte (POINTSIN) + interpolierte Spline-Punkte (POINTSSPLI) |
| 1020 | * Koordinaten relativ: Kurvenanfang = (0,0) |
| 1021 | */ |
| 1022 | // ============================================================ |
| 1023 | // HTML ausgeben |
| 1024 | // ============================================================ |
| 1025 | header('Content-Type: text/html; charset=utf-8'); |
| 1026 | ?> |
| 1027 | <!DOCTYPE html> |
| 1028 | <html lang="de"> |
| 1029 | <head> |
| 1030 | <meta charset="utf-8"> |
| 1031 | <meta name="viewport" content="width=device-width,initial-scale=1"> |
| 1032 | <title>AutoCons – Schnittmusterkonstruktion</title> |
| 1033 | <style> |
| 1034 | @import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Mono:wght@400;500&display=swap'); |
| 1035 | :root{ |
| 1036 | --ink:#1a1209;--paper:#f5f0e8;--sepia:#8b6f47;--rust:#a0522d; |
| 1037 | --vt:#3a5a8a;--rt:#8a3a3a;--grid:#d8d0c0;--btn:#2a3f5f; |
| 1038 | } |
| 1039 | *{box-sizing:border-box;margin:0;padding:0} |
| 1040 | body{background:var(--paper);color:var(--ink);font-family:'DM Mono',monospace;font-size:13px} |
| 1041 | header{background:var(--ink);color:var(--paper);padding:1.2rem 2rem; |
| 1042 | display:flex;align-items:baseline;gap:1rem} |
| 1043 | header h1{font-family:'DM Serif Display',serif;font-size:1.8rem;font-weight:400} |
| 1044 | header p{font-size:.75rem;opacity:.55;letter-spacing:.1em} |
| 1045 | .layout{display:grid;grid-template-columns:1fr 290px;min-height:calc(100vh - 68px)} |
| 1046 | .main{padding:1.2rem 1.4rem 85px;overflow-x:auto} |
| 1047 | .main h2{font-size:.65rem;letter-spacing:.18em;text-transform:uppercase; |
| 1048 | color:var(--sepia);margin-bottom:.8rem} |
| 1049 | svg.sketch{border:1px solid var(--grid);background:#fffef9; |
| 1050 | box-shadow:0 2px 14px rgba(0,0,0,.09);display:block;max-width:100%} |
| 1051 | aside{background:#ede8df;border-left:1px solid var(--grid); |
| 1052 | padding:1.2rem;overflow-y:auto;display:flex;flex-direction:column;gap:1rem} |
| 1053 | aside h3{font-family:'DM Serif Display',serif;font-size:1rem;font-weight:400;color:var(--rust)} |
| 1054 | .form-grid{display:grid;grid-template-columns:1fr 1fr;gap:.15rem .4rem;align-items:center} |
| 1055 | .form-grid label{font-size:.66rem;color:var(--sepia)} |
| 1056 | .form-grid input[type=number]{width:100%;padding:.22rem .35rem;border:1px solid var(--grid); |
| 1057 | background:#faf7f2;font-family:'DM Mono',monospace;font-size:.7rem;color:var(--ink);border-radius:2px} |
| 1058 | .form-grid input:focus{outline:2px solid var(--vt);background:#fff} |
| 1059 | .grp-head{grid-column:1/-1;color:var(--rust);font-size:.65rem;font-weight:600; |
| 1060 | margin-top:.45rem;padding-top:.35rem;border-top:1px solid var(--grid)} |
| 1061 | .btn{width:100%;padding:.5rem;background:var(--btn);color:#fff; |
| 1062 | border:none;cursor:pointer;font-family:'DM Serif Display',serif; |
| 1063 | font-size:.95rem;border-radius:2px;margin-top:.4rem} |
| 1064 | .btn:hover{background:#3a5a8a} |
| 1065 | .legend{font-size:.7rem} |
| 1066 | .legend div{display:flex;align-items:center;gap:.5rem;margin:.22rem 0} |
| 1067 | .legend span{display:inline-block;height:3px;width:20px;flex-shrink:0} |
| 1068 | details{font-size:.7rem} |
| 1069 | summary{color:var(--sepia);cursor:pointer;padding:.25rem 0} |
| 1070 | .logbox{max-height:200px;overflow-y:auto;background:#f5f0e8;border:1px solid var(--grid); |
| 1071 | padding:.4rem;line-height:1.55;color:#666;font-size:.63rem;margin-top:.3rem} |
| 1072 | |
| 1073 | /* Export-Buttons */ |
| 1074 | .export-section{display:flex;flex-direction:column;gap:.4rem} |
| 1075 | .export-section h3{font-family:'DM Serif Display',serif;font-size:1rem;font-weight:400;color:var(--rust)} |
| 1076 | .exp-btn{display:flex;align-items:center;gap:.5rem; |
| 1077 | padding:.4rem .7rem;border:1px solid var(--grid);border-radius:2px; |
| 1078 | background:#faf7f2;cursor:pointer;font-family:'DM Mono',monospace; |
| 1079 | font-size:.72rem;color:var(--ink);text-align:left;width:100%; |
| 1080 | transition:background .15s} |
| 1081 | .exp-btn:hover{background:#e8e0d0;border-color:#b0a080} |
| 1082 | .exp-btn .icon{font-size:1rem;width:20px;text-align:center;flex-shrink:0} |
| 1083 | .exp-btn .lbl{flex:1} |
| 1084 | .exp-btn .hint{font-size:.6rem;color:#999} |
| 1085 | |
| 1086 | /* ---- Druck-CSS ---- */ |
| 1087 | @media print { |
| 1088 | /* Standard: nur Zeichnung */ |
| 1089 | body{background:#fff} |
| 1090 | header{background:#fff!important;color:#000!important;border-bottom:1px solid #ccc} |
| 1091 | .layout{display:block} |
| 1092 | aside{display:none} |
| 1093 | #player{display:none!important} |
| 1094 | svg.sketch{box-shadow:none;border:none;width:100%!important;height:auto!important} |
| 1095 | .main{padding:0} |
| 1096 | |
| 1097 | /* Klasse print-sidebar: nur Sidebar drucken */ |
| 1098 | body.print-sidebar .main{display:none} |
| 1099 | body.print-sidebar aside{display:block;border:none;padding:0} |
| 1100 | body.print-sidebar #player{display:none!important} |
| 1101 | } |
| 1102 | |
| 1103 | /* ---- Player-Leiste ---- */ |
| 1104 | #player{ |
| 1105 | position:fixed;bottom:0;left:0;right:310px; |
| 1106 | background:#1e1608f2;color:#f0ead8; |
| 1107 | font-family:'DM Mono',monospace;font-size:12px; |
| 1108 | padding:.5rem 1rem; |
| 1109 | display:flex;flex-direction:column;gap:.35rem; |
| 1110 | box-shadow:0 -4px 18px rgba(0,0,0,.35); |
| 1111 | z-index:100; |
| 1112 | border-top:2px solid #4a3a20; |
| 1113 | } |
| 1114 | #pl-row0{ |
| 1115 | display:flex;align-items:center;gap:.8rem;flex-wrap:wrap; |
| 1116 | } |
| 1117 | #pl-row1{ |
| 1118 | display:none;align-items:center;gap:.5rem;flex-wrap:wrap; |
| 1119 | } |
| 1120 | #player.active #pl-row1{display:flex;} |
| 1121 | #pl-desc{flex:1;font-size:.75rem;color:#d4c9a8; |
| 1122 | white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0} |
| 1123 | #pl-counter{font-size:.7rem;color:#7a6a4a;white-space:nowrap;min-width:60px;text-align:right} |
| 1124 | .pl-btn{background:#3a5a8a;color:#fff;border:none;cursor:pointer; |
| 1125 | padding:.28rem .65rem;border-radius:2px;font-size:.8rem;line-height:1.4} |
| 1126 | .pl-btn:hover{background:#5578aa} |
| 1127 | .pl-btn:disabled{background:#333;color:#666;cursor:default} |
| 1128 | #pl-range{flex:1;min-width:100px;accent-color:#5a7aaa;cursor:pointer;height:4px} |
| 1129 | #pl-step-type{font-size:.68rem;padding:.15rem .4rem;border-radius:2px; |
| 1130 | background:#2a3f5f;color:#9ab;white-space:nowrap} |
| 1131 | </style> |
| 1132 | </head> |
| 1133 | <body> |
| 1134 | <header> |
| 1135 | <h1>AutoCons</h1> |
| 1136 | <a href="/autocons2/" style=" |
| 1137 | font-family:'DM Mono',monospace;font-size:.72rem; |
| 1138 | color:rgba(255,255,255,.6);text-decoration:none; |
| 1139 | border:1px solid rgba(255,255,255,.25);padding:.25rem .65rem; |
| 1140 | border-radius:2px;white-space:nowrap" |
| 1141 | title="Zum Konstruktions-Editor v0.5"> |
| 1142 | ✎ Editor v0.5 |
| 1143 | </a> |
| 1144 | <p>Schnittmusterkonstruktion · Vorderteil & Rückenteil</p> |
| 1145 | </header> |
| 1146 | <div class="layout"> |
| 1147 | |
| 1148 | <!-- ===== HAUPTZEICHNUNG ===== --> |
| 1149 | <section class="main"> |
| 1150 | <h2 id="sketch-title">Konstruktionsskizze</h2> |
| 1151 | |
| 1152 | <?php |
| 1153 | // ----- SVG öffnen ----- |
| 1154 | echo "<svg class='sketch' id='mainsvg' width='$SVG_W' height='$SVG_H' xmlns='http://www.w3.org/2000/svg'>\n"; |
| 1155 | |
| 1156 | // Raster |
| 1157 | $step=5*$scale; |
| 1158 | echo "<g id='grid' stroke='#ece6d8' stroke-width='.4'>\n"; |
| 1159 | for($gx=$margin;$gx<$SVG_W;$gx+=$step) |
| 1160 | echo "<line x1='".round($gx)."' y1='0' x2='".round($gx)."' y2='$SVG_H'/>\n"; |
| 1161 | for($gy=0;$gy<$SVG_H;$gy+=$step) |
| 1162 | echo "<line x1='0' y1='".round($gy)."' x2='$SVG_W' y2='".round($gy)."'/>\n"; |
| 1163 | echo "</g>\n"; |
| 1164 | |
| 1165 | // ---- Konstruktionsschritte als einzelne <g>-Gruppen ---- |
| 1166 | // Alle zunächst sichtbar (Normalzustand = fertige Zeichnung) |
| 1167 | // Im Player-Modus werden sie animiert ein-/ausgeblendet |
| 1168 | echo "<g id='steps-layer'>\n"; |
| 1169 | foreach($svgSteps as $si=>$st){ |
| 1170 | $pid='s'.($si+1); |
| 1171 | echo "<g id='$pid' class='stepgrp' data-fn='{$st['fn']}' data-part='{$st['part']}'>" |
| 1172 | .$st['svg']."</g>\n"; |
| 1173 | } |
| 1174 | echo "</g>\n"; |
| 1175 | |
| 1176 | // ---- Kontur-Layer (VT + RT) ---- immer sichtbar im Normalmodus |
| 1177 | echo "<g id='contour-layer'>\n"; |
| 1178 | |
| 1179 | // VT interne Linien |
| 1180 | echo "<g id='vt-internal'>\n"; |
| 1181 | foreach($vt_internal as $il) echo svgIL($il[0],$il[1],$P_,$scale,$vtOx,$Oy,'#7a9abf'); |
| 1182 | echo "</g>\n"; |
| 1183 | |
| 1184 | // VT Konturpfad |
| 1185 | echo "<path id='vt-path' d='$vtPath' fill='rgba(58,90,138,.07)' stroke='#3a5a8a'" |
| 1186 | ." stroke-width='2' stroke-linejoin='round' fill-rule='evenodd'/>\n"; |
| 1187 | |
| 1188 | // VT Beschriftungen |
| 1189 | echo "<g id='vt-labels'>\n"; |
| 1190 | foreach($vt_lbl_map as $nr=>$lbl) echo svgLbl($nr,$lbl,$P_,$scale,$vtOx,$Oy,'#3a5a8a'); |
| 1191 | echo "</g>\n"; |
| 1192 | |
| 1193 | // VT Wasserzeichen |
| 1194 | $cx=0;$cy=0;$cnt=0; |
| 1195 | foreach($vt_contour as $cp){$nr=$cp['nr'];if($P_[$nr][0]<1e14){$cx+=$P_[$nr][0];$cy+=$P_[$nr][1];$cnt++;}} |
| 1196 | if($cnt){$tx=round($cx/$cnt*$scale+$vtOx);$ty=round(-$cy/$cnt*$scale+$Oy); |
| 1197 | echo "<text x='$tx' y='$ty' text-anchor='middle' font-family='DM Serif Display,serif'" |
| 1198 | ." font-size='30' fill='rgba(58,90,138,.12)' font-style='italic'>VT</text>\n";} |
| 1199 | |
| 1200 | // RT interne Linien |
| 1201 | echo "<g id='rt-internal'>\n"; |
| 1202 | foreach($rt_internal as $il) echo svgIL($il[0],$il[1],$P_,$scale,$rtOx,$Oy,'#bf7a7a'); |
| 1203 | echo "</g>\n"; |
| 1204 | |
| 1205 | // RT Konturpfad |
| 1206 | echo "<path id='rt-path' d='$rtPath' fill='rgba(138,58,58,.07)' stroke='#8a3a3a'" |
| 1207 | ." stroke-width='2' stroke-linejoin='round' fill-rule='evenodd'/>\n"; |
| 1208 | |
| 1209 | // RT Beschriftungen |
| 1210 | echo "<g id='rt-labels'>\n"; |
| 1211 | foreach($rt_lbl_map as $nr=>$lbl) echo svgLbl($nr,$lbl,$P_,$scale,$rtOx,$Oy,'#8a3a3a'); |
| 1212 | echo "</g>\n"; |
| 1213 | |
| 1214 | // RT Wasserzeichen |
| 1215 | $cx=0;$cy=0;$cnt=0; |
| 1216 | foreach($rt_contour as $cp){$nr=$cp['nr'];if($P_[$nr][0]<1e14){$cx+=$P_[$nr][0];$cy+=$P_[$nr][1];$cnt++;}} |
| 1217 | if($cnt){$tx=round($cx/$cnt*$scale+$rtOx);$ty=round(-$cy/$cnt*$scale+$Oy); |
| 1218 | echo "<text x='$tx' y='$ty' text-anchor='middle' font-family='DM Serif Display,serif'" |
| 1219 | ." font-size='30' fill='rgba(138,58,58,.12)' font-style='italic'>RT</text>\n";} |
| 1220 | |
| 1221 | echo "</g>\n"; // contour-layer |
| 1222 | |
| 1223 | // Maßstab |
| 1224 | $barX=$margin;$barY=$SVG_H-14;$barLen=10*$scale; |
| 1225 | echo "<g stroke='#888' stroke-width='1.5'>\n"; |
| 1226 | echo "<line x1='$barX' y1='".($barY-4)."' x2='$barX' y2='".($barY+4)."'/>\n"; |
| 1227 | echo "<line x1='".round($barX+$barLen)."' y1='".($barY-4)."' x2='".round($barX+$barLen)."' y2='".($barY+4)."'/>\n"; |
| 1228 | echo "<line x1='$barX' y1='$barY' x2='".round($barX+$barLen)."' y2='$barY'/>\n"; |
| 1229 | echo "</g>\n"; |
| 1230 | echo "<text x='".round($barX+$barLen/2)."' y='".($barY-7)."' text-anchor='middle'" |
| 1231 | ." font-family='DM Mono,monospace' font-size='9' fill='#777'>10 cm</text>\n"; |
| 1232 | |
| 1233 | echo "</svg>\n"; // Ende SVG |
| 1234 | ?> |
| 1235 | |
| 1236 | |
| 1237 | |
| 1238 | </section><!-- main --> |
| 1239 | |
| 1240 | <!-- ===== SIDEBAR ===== --> |
| 1241 | <aside> |
| 1242 | <?php |
| 1243 | echo "<div>\n<h3>Maßtabelle</h3>\n"; |
| 1244 | echo "<form method='post' action=''>\n"; |
| 1245 | echo "<div class='form-grid'>\n"; |
| 1246 | $groups=[ |
| 1247 | 'Körpermaße'=>[ |
| 1248 | 'uBr'=>'Brustumfang cm','uGe'=>'Gesäßumfang cm', |
| 1249 | 'lKoe'=>'Körperlänge cm','uTa'=>'Taillenumfang cm', |
| 1250 | 'lTa'=>'Taillenlänge cm','lVo'=>'Vorderlänge cm', |
| 1251 | 'uHa'=>'Halsumfang cm','lSu'=>'Schulterlänge cm', |
| 1252 | 'lGe'=>'Gesäßlänge cm','alfaSu'=>'Schulterwinkel °', |
| 1253 | ], |
| 1254 | 'Modell-Parameter'=>[ |
| 1255 | 'zBr'=>'Brustbreitenzug.','hPo'=>'Polsterhöhe', |
| 1256 | 'bTAv'=>'Taillenausfall v.','bTAh'=>'Taillenausfall h.', |
| 1257 | 'VHtv'=>'Hloch-Tief vorn','VHth'=>'Hloch-Tief hint.', |
| 1258 | 'VHh'=>'Hloch-Hoch','bSuM'=>'Schultermod.', |
| 1259 | 'bUb'=>'Übertrittbreite','hSaT'=>'Saumlinie', |
| 1260 | ], |
| 1261 | ]; |
| 1262 | foreach($groups as $grpName=>$fields){ |
| 1263 | echo "<div class='grp-head'>$grpName</div>\n"; |
| 1264 | foreach($fields as $k=>$label){ |
| 1265 | $v=htmlspecialchars((string)$vals[$k]); |
| 1266 | echo "<label for='f_$k'>$label</label>\n"; |
| 1267 | echo "<input type='number' id='f_$k' name='$k' step='0.01' value='$v'>\n"; |
| 1268 | } |
| 1269 | } |
| 1270 | echo "</div>\n"; |
| 1271 | echo "<button class='btn' type='submit'>↻ Konstruiere</button>\n"; |
| 1272 | echo "</form>\n</div>\n"; |
| 1273 | |
| 1274 | // Player-Checkbox (wird unter das SVG verschoben) |
| 1275 | $totalSteps=count($svgSteps); |
| 1276 | |
| 1277 | // Legende |
| 1278 | echo "<div class='legend'>\n<h3>Legende</h3>\n"; |
| 1279 | echo "<div><span style='background:#3a5a8a'></span>Vorderteil (VT)</div>\n"; |
| 1280 | echo "<div><span style='background:#8a3a3a'></span>Rückenteil (RT)</div>\n"; |
| 1281 | echo "<div><span style='border-top:1px dashed #c8960a;display:inline-block;width:20px;height:3px'></span>Hilfslinien</div>\n"; |
| 1282 | echo "<div><span style='border-top:1px dashed #2090c0;display:inline-block;width:20px;height:3px'></span>Hilfskreise</div>\n"; |
| 1283 | echo "</div>\n"; |
| 1284 | |
| 1285 | // Protokoll |
| 1286 | echo "<details open>\n<summary>Konstruktionsprotokoll</summary>\n"; |
| 1287 | echo "<div class='logbox'>\n"; |
| 1288 | foreach($log as $l) echo htmlspecialchars($l)."<br>\n"; |
| 1289 | echo "</div></details>\n"; |
| 1290 | |
| 1291 | // Konturpunkte |
| 1292 | echo "<details>\n<summary>Konturpunkte VT</summary>\n<div class='logbox'>\n"; |
| 1293 | foreach($vt_contour as $cp){ |
| 1294 | $nr=$cp['nr']; |
| 1295 | if($P_[$nr][0]<1e14 && $cp['lbl']!=='') |
| 1296 | printf("<div><b>%s</b> x=%.2f y=%.2f%s</div>\n", |
| 1297 | $cp['lbl'],$P_[$nr][0],$P_[$nr][1], |
| 1298 | $cp['info']?" [{$cp['info']}]":''); |
| 1299 | } |
| 1300 | echo "</div></details>\n"; |
| 1301 | |
| 1302 | echo "<details>\n<summary>Konturpunkte RT</summary>\n<div class='logbox'>\n"; |
| 1303 | foreach($rt_contour as $cp){ |
| 1304 | $nr=$cp['nr']; |
| 1305 | if($P_[$nr][0]<1e14 && $cp['lbl']!=='') |
| 1306 | printf("<div><b>%s</b> x=%.2f y=%.2f%s</div>\n", |
| 1307 | $cp['lbl'],$P_[$nr][0],$P_[$nr][1], |
| 1308 | $cp['info']?" [{$cp['info']}]":''); |
| 1309 | } |
| 1310 | echo "</div></details>\n"; |
| 1311 | |
| 1312 | // ---- Export-Sektion ---- |
| 1313 | echo "<div class='export-section'>\n"; |
| 1314 | echo "<h3>Export & Druck</h3>\n"; |
| 1315 | |
| 1316 | // Button: Zeichnung als PNG |
| 1317 | echo "<button class='exp-btn' onclick='exportPNG()'> |
| 1318 | <span class='icon'>🖼</span> |
| 1319 | <span class='lbl'>Zeichnung als PNG<br><span class='hint'>Schnittmuster-Skizze</span></span> |
| 1320 | </button>\n"; |
| 1321 | |
| 1322 | // Button: Zeichnung als PDF |
| 1323 | echo "<button class='exp-btn' onclick='exportPDF()'> |
| 1324 | <span class='icon'>📄</span> |
| 1325 | <span class='lbl'>Zeichnung als PDF<br><span class='hint'>A4 quer, druckfertig</span></span> |
| 1326 | </button>\n"; |
| 1327 | |
| 1328 | // Button: Drucken (Zeichnung) |
| 1329 | echo "<button class='exp-btn' onclick='printSketch()'> |
| 1330 | <span class='icon'>🖨</span> |
| 1331 | <span class='lbl'>Zeichnung drucken<br><span class='hint'>Nur Hauptfenster</span></span> |
| 1332 | </button>\n"; |
| 1333 | |
| 1334 | // Button: Sidebar drucken |
| 1335 | echo "<button class='exp-btn' onclick='printSidebar()'> |
| 1336 | <span class='icon'>🖨</span> |
| 1337 | <span class='lbl'>Maßtabelle drucken<br><span class='hint'>Nur rechter Bereich</span></span> |
| 1338 | </button>\n"; |
| 1339 | |
| 1340 | // Button: Konstruktionsdaten herunterladen (PHP-Link) |
| 1341 | echo "<button class='exp-btn' onclick='exportData()'> |
| 1342 | <span class='icon'>📋</span> |
| 1343 | <span class='lbl'>Konstruktionstext<br><span class='hint'>Maße, Schritte & Punkte als Textdatei</span></span> |
| 1344 | </button>\n"; |
| 1345 | echo "<button class='exp-btn' onclick='printA4()'> |
| 1346 | <span class='icon'>📐</span> |
| 1347 | <span class='lbl'>A4-Ausdruck (1:5)<br><span class='hint'>Mehrere Seiten zum Zusammenkleben</span></span> |
| 1348 | </button>\n"; |
| 1349 | |
| 1350 | echo "<a class='exp-btn' href='?source=1' target='_blank'> |
| 1351 | <span class='icon'>📜</span> |
| 1352 | <span class='lbl'>PHP-Quellcode<br><span class='hint'>autocons.php im Browser anzeigen</span></span> |
| 1353 | </a>\n"; |
| 1354 | |
| 1355 | echo "</div>\n"; |
| 1356 | ?> |
| 1357 | </aside> |
| 1358 | </div><!-- layout --> |
| 1359 | |
| 1360 | <!-- ===== PLAYER-LEISTE (unten) ===== --> |
| 1361 | <div id="player"> |
| 1362 | <div id="pl-row0" style="display:flex;align-items:center;gap:.7rem;padding-bottom:.3rem;border-bottom:1px solid #3a3020;margin-bottom:.3rem"> |
| 1363 | <label style="display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.82rem;color:#d4c9a8"> |
| 1364 | <input type='checkbox' id='cb-play' |
| 1365 | style='width:15px;height:15px;accent-color:#5a7aaa;cursor:pointer'> |
| 1366 | <span>▶ Konstruktionsablauf</span> |
| 1367 | </label> |
| 1368 | <span style="font-size:.68rem;color:#6a5a3a"><?= $totalSteps ?> Schritte · ←→ Pfeiltasten · Leertaste Play/Pause · Esc Schließen</span> |
| 1369 | </div> |
| 1370 | <div id="pl-row1"> |
| 1371 | <button class="pl-btn" id="pl-rew" title="Zum Anfang">◀◀</button> |
| 1372 | <button class="pl-btn" id="pl-prev" title="Zurück (←)">◀</button> |
| 1373 | <button class="pl-btn" id="pl-play" title="Play/Pause (Leertaste)">▶</button> |
| 1374 | <button class="pl-btn" id="pl-next" title="Vorwärts (→)">▶▶</button> |
| 1375 | <input type="range" id="pl-range" min="0" value="-1"> |
| 1376 | <span id="pl-counter">0 / <?= $totalSteps ?></span> |
| 1377 | <span id="pl-step-type">–</span> |
| 1378 | <span id="pl-desc">–</span> |
| 1379 | </div> |
| 1380 | </div> |
| 1381 | |
| 1382 | <script> |
| 1383 | (function(){ |
| 1384 | // Alle Step-Gruppen sammeln |
| 1385 | const grps = Array.from(document.querySelectorAll('.stepgrp')); |
| 1386 | const total = grps.length; |
| 1387 | const contourLayer = document.getElementById('contour-layer'); |
| 1388 | const player = document.getElementById('player'); |
| 1389 | const cbPlay = document.getElementById('cb-play'); |
| 1390 | const range = document.getElementById('pl-range'); |
| 1391 | const counter = document.getElementById('pl-counter'); |
| 1392 | const descEl = document.getElementById('pl-desc'); |
| 1393 | const typeEl = document.getElementById('pl-step-type'); |
| 1394 | const btnPrev = document.getElementById('pl-prev'); |
| 1395 | const btnNext = document.getElementById('pl-next'); |
| 1396 | const btnPlay = document.getElementById('pl-play'); |
| 1397 | const btnRew = document.getElementById('pl-rew'); |
| 1398 | |
| 1399 | range.max = total; // 0 = vor Schritt 1, 1..total = nach Schritt N |
| 1400 | |
| 1401 | let cur = -1; // -1 = Ausgangszustand (alle sichtbar) |
| 1402 | let playing = false; |
| 1403 | let timer = null; |
| 1404 | const SPEED = 600; // ms pro Schritt |
| 1405 | |
| 1406 | // Setzt den Zustand auf Schritt idx (0-basiert, -1 = alle aus) |
| 1407 | function applyStep(idx){ |
| 1408 | grps.forEach((g, i) => { |
| 1409 | g.style.display = (i <= idx) ? '' : 'none'; |
| 1410 | }); |
| 1411 | // Kontur: nur zeigen wenn alle Schritte durch (idx === total-1) |
| 1412 | // oder im Normalmodus (idx === -1 bedeutet Player nicht aktiv) |
| 1413 | const showContour = (idx < 0 || idx >= total - 1); |
| 1414 | contourLayer.style.display = showContour ? '' : 'none'; |
| 1415 | } |
| 1416 | |
| 1417 | function show(idx){ |
| 1418 | cur = Math.max(-1, Math.min(total-1, idx)); |
| 1419 | applyStep(cur); |
| 1420 | range.value = cur + 1; // Slider: 0=Start, 1..total=Schritt |
| 1421 | if(cur < 0){ |
| 1422 | counter.textContent = '0 / ' + total; |
| 1423 | descEl.textContent = 'Startposition'; |
| 1424 | typeEl.textContent = '–'; |
| 1425 | typeEl.style.background = '#2a3f5f'; |
| 1426 | } else { |
| 1427 | const g = grps[cur]; |
| 1428 | const fn = g.dataset.fn || '–'; |
| 1429 | const part = g.dataset.part || ''; |
| 1430 | // Beschreibung aus dem title-Attribut (wir lesen sie aus dem PHP-Datenarray) |
| 1431 | counter.textContent = (cur+1) + ' / ' + total; |
| 1432 | typeEl.textContent = fn + (part ? ' ['+part+']' : ''); |
| 1433 | typeEl.style.background = part==='RT' ? '#5a2a2a' : '#2a3f5f'; |
| 1434 | // Beschreibung: hole aus dem globalen DESCS-Array |
| 1435 | descEl.textContent = DESCS[cur] || ''; |
| 1436 | } |
| 1437 | btnPrev.disabled = (cur <= -1); |
| 1438 | btnNext.disabled = (cur >= total-1); |
| 1439 | btnRew .disabled = (cur <= -1); |
| 1440 | } |
| 1441 | |
| 1442 | function startPlay(){ |
| 1443 | playing = true; |
| 1444 | btnPlay.innerHTML = '▮▮'; |
| 1445 | btnPlay.title = 'Pause (Leertaste)'; |
| 1446 | timer = setInterval(()=>{ |
| 1447 | if(cur >= total-1){ stopPlay(); return; } |
| 1448 | show(cur+1); |
| 1449 | }, SPEED); |
| 1450 | } |
| 1451 | function stopPlay(){ |
| 1452 | playing = false; |
| 1453 | btnPlay.innerHTML = '▶'; |
| 1454 | btnPlay.title = 'Play (Leertaste)'; |
| 1455 | clearInterval(timer); |
| 1456 | } |
| 1457 | |
| 1458 | function openPlayer(){ |
| 1459 | player.classList.add('active'); |
| 1460 | show(-1); // Startposition: alle Schritte unsichtbar, Kontur sichtbar ... nein: |
| 1461 | // Wir starten mit leerer Zeichnung (Schritte alle aus, Kontur aus) |
| 1462 | applyStep(-1); |
| 1463 | contourLayer.style.display = 'none'; |
| 1464 | show(-1); |
| 1465 | } |
| 1466 | function closePlayer(){ |
| 1467 | stopPlay(); |
| 1468 | player.classList.remove('active'); |
| 1469 | // Alles wieder normal |
| 1470 | grps.forEach(g => g.style.display = ''); |
| 1471 | contourLayer.style.display = ''; |
| 1472 | cur = -1; |
| 1473 | } |
| 1474 | |
| 1475 | cbPlay.addEventListener('change', function(){ |
| 1476 | if(this.checked) openPlayer(); |
| 1477 | else closePlayer(); |
| 1478 | }); |
| 1479 | |
| 1480 | btnPlay.onclick = ()=>{ playing ? stopPlay() : startPlay(); }; |
| 1481 | |
| 1482 | document.addEventListener('keydown', e => { |
| 1483 | if(!cbPlay.checked) return; |
| 1484 | if(e.key==='ArrowRight'||e.key==='ArrowDown'){ stopPlay(); show(cur+1); e.preventDefault(); } |
| 1485 | if(e.key==='ArrowLeft' ||e.key==='ArrowUp') { stopPlay(); show(cur-1); e.preventDefault(); } |
| 1486 | if(e.key===' ') { playing?stopPlay():startPlay(); e.preventDefault(); } |
| 1487 | if(e.key==='Escape') { cbPlay.checked=false; closePlayer(); } |
| 1488 | }); |
| 1489 | |
| 1490 | // Puls-Effekt: neues Element kurz aufblinken |
| 1491 | function pulseStep(idx){ |
| 1492 | show(idx); |
| 1493 | if(idx >= 0 && idx < grps.length){ |
| 1494 | const g = grps[idx]; |
| 1495 | g.style.opacity = '0.2'; |
| 1496 | let op = 0.2; |
| 1497 | const pulse = setInterval(()=>{ |
| 1498 | op = Math.min(1, op + 0.12); |
| 1499 | g.style.opacity = op; |
| 1500 | if(op >= 1){ clearInterval(pulse); g.style.removeProperty('opacity'); } |
| 1501 | }, 25); |
| 1502 | } |
| 1503 | } |
| 1504 | // Button-Handler mit Puls |
| 1505 | btnPrev.onclick = ()=>{ stopPlay(); pulseStep(cur-1); }; |
| 1506 | btnNext.onclick = ()=>{ stopPlay(); pulseStep(cur+1); }; |
| 1507 | btnRew .onclick = ()=>{ stopPlay(); pulseStep(-1); }; |
| 1508 | range.oninput = ()=>{ stopPlay(); pulseStep(parseInt(range.value)-1); }; |
| 1509 | |
| 1510 | })(); |
| 1511 | </script> |
| 1512 | |
| 1513 | <?php |
| 1514 | // DESCS-Array für JavaScript (Beschreibungstexte je Schritt) |
| 1515 | echo "<script>\nconst DESCS = [\n"; |
| 1516 | foreach($svgSteps as $i=>$st){ |
| 1517 | echo " ".json_encode($st['desc'],JSON_UNESCAPED_UNICODE); |
| 1518 | echo ($i<count($svgSteps)-1)?",\n":"\n"; |
| 1519 | } |
| 1520 | echo "];\n</script>\n"; |
| 1521 | ?> |
| 1522 | |
| 1523 | <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
| 1524 | <script> |
| 1525 | // ================================================================ |
| 1526 | // Export-Funktionen |
| 1527 | // ================================================================ |
| 1528 | |
| 1529 | function svgToCanvas(svgEl, scale, callback) { |
| 1530 | const W = parseFloat(svgEl.getAttribute('width')); |
| 1531 | const H = parseFloat(svgEl.getAttribute('height')); |
| 1532 | const canvas = document.createElement('canvas'); |
| 1533 | canvas.width = W * scale; |
| 1534 | canvas.height = H * scale; |
| 1535 | const ctx = canvas.getContext('2d'); |
| 1536 | ctx.fillStyle = '#fffef9'; |
| 1537 | ctx.fillRect(0, 0, canvas.width, canvas.height); |
| 1538 | const svgData = new XMLSerializer().serializeToString(svgEl); |
| 1539 | const blob = new Blob([svgData], {type:'image/svg+xml;charset=utf-8'}); |
| 1540 | const url = URL.createObjectURL(blob); |
| 1541 | const img = new Image(); |
| 1542 | img.onload = function() { |
| 1543 | ctx.scale(scale, scale); |
| 1544 | ctx.drawImage(img, 0, 0); |
| 1545 | URL.revokeObjectURL(url); |
| 1546 | callback(canvas, W, H); |
| 1547 | }; |
| 1548 | img.onerror = () => { alert('SVG konnte nicht gerendert werden.'); }; |
| 1549 | img.src = url; |
| 1550 | } |
| 1551 | |
| 1552 | function exportPNG() { |
| 1553 | svgToCanvas(document.getElementById('mainsvg'), 2, function(canvas) { |
| 1554 | const a = document.createElement('a'); |
| 1555 | a.download = 'autocons_zeichnung.png'; |
| 1556 | a.href = canvas.toDataURL('image/png'); |
| 1557 | a.click(); |
| 1558 | }); |
| 1559 | } |
| 1560 | |
| 1561 | function exportPDF() { |
| 1562 | svgToCanvas(document.getElementById('mainsvg'), 3, function(canvas, svgW, svgH) { |
| 1563 | const { jsPDF } = window.jspdf; |
| 1564 | const doc = new jsPDF({ orientation:'landscape', unit:'mm', format:'a4' }); |
| 1565 | const pageW=297, pageH=210, margin=8, headH=12; |
| 1566 | const availW = pageW - 2*margin; |
| 1567 | const availH = pageH - 2*margin - headH; |
| 1568 | const ratio = Math.min(availW/(svgW*0.264583), availH/(svgH*0.264583)); |
| 1569 | const pdfW = svgW * 0.264583 * ratio; |
| 1570 | const pdfH = svgH * 0.264583 * ratio; |
| 1571 | // Kopfzeile |
| 1572 | doc.setFontSize(11); doc.setTextColor(40,25,8); |
| 1573 | doc.text('AutoCons – Schnittmusterkonstruktion', margin, margin+6); |
| 1574 | doc.setFontSize(8); doc.setTextColor(130,100,60); |
| 1575 | doc.text(new Date().toLocaleDateString('de-DE'), pageW-margin, margin+6, {align:'right'}); |
| 1576 | doc.setDrawColor(180,155,110); |
| 1577 | doc.line(margin, margin+8, pageW-margin, margin+8); |
| 1578 | // Zeichnung |
| 1579 | doc.addImage(canvas.toDataURL('image/png'), 'PNG', margin, margin+headH, pdfW, pdfH); |
| 1580 | doc.save('autocons_zeichnung.pdf'); |
| 1581 | }); |
| 1582 | } |
| 1583 | |
| 1584 | function printSketch() { |
| 1585 | document.body.classList.remove('print-sidebar'); |
| 1586 | window.print(); |
| 1587 | } |
| 1588 | |
| 1589 | function printSidebar() { |
| 1590 | document.body.classList.add('print-sidebar'); |
| 1591 | window.print(); |
| 1592 | setTimeout(() => document.body.classList.remove('print-sidebar'), 1200); |
| 1593 | } |
| 1594 | |
| 1595 | function exportData() { |
| 1596 | // Punktnamen-Map (Nummer -> Name) |
| 1597 | const PN = { |
| 1598 | 100:'A',101:'B',102:'C',103:'D',104:'E',105:'F',106:'G',107:'H', |
| 1599 | 108:'I',109:'J',110:'K',111:'L',112:'M',113:'N',114:'O', |
| 1600 | 115:'P1',116:'P10',117:'P11',118:'P11a',119:'P11i',120:'P12', |
| 1601 | 121:'P13',122:'P14',123:'P15',124:'P16',125:'P17',126:'P18', |
| 1602 | 127:'P19',128:'P2',129:'P20',130:'P21',131:'P22',132:'P23', |
| 1603 | 133:'P3',134:'P4',135:'P5',136:'P6',137:'P7',138:'P9',139:'Ppkt', |
| 1604 | 200:'A',201:'B',202:'C',203:'D',204:'E',205:'F',206:'G',207:'H', |
| 1605 | 208:'I',209:'J',210:'K',211:'L',212:'M',213:'N',214:'O', |
| 1606 | 215:'P1',216:'P10',217:'P11',218:'P12',219:'P13',220:'P14', |
| 1607 | 221:'P15',222:'P16',223:'P17',224:'P18',225:'P18a',226:'P18i', |
| 1608 | 227:'P19',228:'P2',229:'P20',230:'P21',231:'P22',232:'P23', |
| 1609 | 233:'P24',234:'P25',235:'P3',236:'P4',237:'P5',238:'P6', |
| 1610 | 239:'P7',240:'P8',241:'P9a',242:'P9i', |
| 1611 | }; |
| 1612 | function pn(nr) { const n=parseInt(nr); return (PN[n]||('P'+n))+'('+n+')'; } |
| 1613 | |
| 1614 | // Log-Eintrag in lesbaren Satz übersetzen |
| 1615 | function translateLog(entry) { |
| 1616 | // HTML-Entities dekodieren |
| 1617 | entry = entry.replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); |
| 1618 | |
| 1619 | // PW(nr, x, y) |
| 1620 | let m = entry.match(/^PW\((\d+),\s*([\d.\-]+),\s*([\d.\-]+)\)$/); |
| 1621 | if(m) return `Punkt ${pn(m[1])} gesetzt auf (x=${m[2]}, y=${m[3]}) cm`; |
| 1622 | |
| 1623 | // PPW(nr1 <- nr2 +(dx,dy)) => (x,y) |
| 1624 | m = entry.match(/^PPW\((\d+)\s*<-\s*(\d+)\s*\+\(([\d.\-]+),([\d.\-]+)\)\)\s*=>\s*\(([\d.\-]+),([\d.\-]+)\)/); |
| 1625 | if(m) return `Punkt ${pn(m[1])} = ${pn(m[2])} verschoben um (${m[3]}, ${m[4]}) => (${m[5]}, ${m[6]}) cm`; |
| 1626 | |
| 1627 | // PD(Pnr = Ppalt gedreht um Pmp, grad°) |
| 1628 | m = entry.match(/^PD\(P(\d+) = P(\d+) gedreht um P(\d+),\s*([\d.\-]+)°\)/); |
| 1629 | if(m) return `Punkt ${pn(m[1])} = ${pn(m[2])} gedreht um ${pn(m[3])} um ${m[4]}°`; |
| 1630 | |
| 1631 | // PLL(Pnr = Schnitt Lx/Ly) => (x,y) |
| 1632 | m = entry.match(/^PLL\(P(\d+) = Schnitt L(\d+)\/L(\d+)\)\s*=>\s*\(([\d.\-]+),([\d.\-]+)\)/); |
| 1633 | if(m) return `Punkt ${pn(m[1])} = Schnittpunkt L${m[2]} / L${m[3]} => (${m[4]}, ${m[5]}) cm`; |
| 1634 | |
| 1635 | // PZP(Pneu auf Pp1..Pp2, x%) |
| 1636 | m = entry.match(/^PZP\(P(\d+) auf P(\d+)\.\.P(\d+),\s*([\d.\-]+)%\)/); |
| 1637 | if(m) return `Punkt ${pn(m[1])} auf Strecke ${pn(m[2])}–${pn(m[3])} bei ${m[4]}%`; |
| 1638 | |
| 1639 | // PKK(Pnr = Schnitt Kx/Ky, sp=n) => (x,y) |
| 1640 | m = entry.match(/^PKK\(P(\d+) = Schnitt K(\d+)\/K(\d+), sp=(\d+)\)\s*=>\s*\(([\d.\-]+),([\d.\-]+)\)/); |
| 1641 | if(m) return `Punkt ${pn(m[1])} = Schnitt Kreis K${m[2]} / K${m[3]} (Seite ${m[4]}) => (${m[5]}, ${m[6]}) cm`; |
| 1642 | |
| 1643 | // PKL(Pnr = Schnitt Kx/Ly, sp=n) => (x,y) |
| 1644 | m = entry.match(/^PKL\(P(\d+) = Schnitt K(\d+)\/L(\d+), sp=(\d+)\)\s*=>\s*\(([\d.\-]+),([\d.\-]+)\)/); |
| 1645 | if(m) return `Punkt ${pn(m[1])} = Schnitt Kreis K${m[2]} / Linie L${m[3]} (Seite ${m[4]}) => (${m[5]}, ${m[6]}) cm`; |
| 1646 | |
| 1647 | // LPP(Lnr durch Px und Py) |
| 1648 | m = entry.match(/^LPP\(L(\d+) durch P(\d+) und P(\d+)\)/); |
| 1649 | if(m) return `Linie L${m[1]} = durch ${pn(m[2])} und ${pn(m[3])}`; |
| 1650 | |
| 1651 | // LSP(Lnr senkrecht zu Lx durch Pnr) |
| 1652 | m = entry.match(/^LSP\(L(\d+) senkrecht zu L(\d+) durch P(\d+)\)/); |
| 1653 | if(m) return `Linie L${m[1]} = senkrecht zu L${m[2]} durch ${pn(m[3])}`; |
| 1654 | |
| 1655 | // LPDP(Lnr parallel zu Lx durch Pnr) |
| 1656 | m = entry.match(/^LPDP\(L(\d+) parallel zu L(\d+) durch P(\d+)\)/); |
| 1657 | if(m) return `Linie L${m[1]} = parallel zu L${m[2]} durch ${pn(m[3])}`; |
| 1658 | |
| 1659 | // LPA(Lnr parallel zu Lx, Abstand=d) |
| 1660 | m = entry.match(/^LPA\(L(\d+) parallel zu L(\d+), Abstand=([\d.\-]+)\)/); |
| 1661 | if(m) return `Linie L${m[1]} = parallel zu L${m[2]}, Abstand ${m[3]} cm`; |
| 1662 | |
| 1663 | // KR(Knr Mittelpunkt Pnr, r=...) |
| 1664 | m = entry.match(/^KR\(K(\d+) Mittelpunkt P(\d+), r=([\d.\-]+)\)/); |
| 1665 | if(m) return `Kreis K${m[1]} = Mittelpunkt ${pn(m[2])}, Radius ${m[3]} cm`; |
| 1666 | |
| 1667 | // === Abschnittstitel |
| 1668 | if(entry.includes('VORDERTEIL')) return '\n--- VORDERTEIL (VT) ---'; |
| 1669 | if(entry.includes('RÜCKENTEIL')) return '\n--- RÜCKENTEIL (RT) ---'; |
| 1670 | |
| 1671 | // Fallback: Original |
| 1672 | return entry; |
| 1673 | } |
| 1674 | |
| 1675 | const lines = []; |
| 1676 | const hr44 = '='.repeat(44); |
| 1677 | const hr44m = '-'.repeat(44); |
| 1678 | |
| 1679 | lines.push('AutoCons - Konstruktionsdaten'); |
| 1680 | lines.push(hr44); |
| 1681 | lines.push('Datum: ' + new Date().toLocaleString('de-DE')); |
| 1682 | |
| 1683 | // 1. Masstafel |
| 1684 | lines.push(''); |
| 1685 | lines.push('MASSTAFEL'); |
| 1686 | lines.push(hr44m); |
| 1687 | document.querySelectorAll('.form-grid input[type=number]').forEach(inp => { |
| 1688 | const lbl = inp.previousElementSibling; |
| 1689 | const name = (lbl ? lbl.textContent.trim() : inp.name).padEnd(28); |
| 1690 | lines.push(name + inp.value); |
| 1691 | }); |
| 1692 | |
| 1693 | // 2. Konstruktionstext (übersetzt) |
| 1694 | lines.push(''); |
| 1695 | lines.push('KONSTRUKTIONSABLAUF'); |
| 1696 | lines.push(hr44m); |
| 1697 | lines.push('Alle Maße in cm. Koordinaten: x = nach rechts, y = nach oben.'); |
| 1698 | lines.push(''); |
| 1699 | |
| 1700 | // Protokoll aus dem ersten logbox auslesen |
| 1701 | const logBox = document.querySelector('details .logbox'); |
| 1702 | if(logBox) { |
| 1703 | const raw = logBox.innerHTML |
| 1704 | .split('<br>').map(s => s.replace(/<[^>]+>/g,'').trim()).filter(s => s.length > 0); |
| 1705 | raw.forEach((entry, i) => { |
| 1706 | const translated = translateLog(entry); |
| 1707 | if(translated.startsWith('\n')) { |
| 1708 | lines.push(''); |
| 1709 | lines.push(translated.trim()); |
| 1710 | lines.push(hr44m); |
| 1711 | } else { |
| 1712 | // Zeilennummer + Originaltext + Übersetzung |
| 1713 | lines.push(String(i+1).padStart(3) + '. ' + translated); |
| 1714 | } |
| 1715 | }); |
| 1716 | } |
| 1717 | |
| 1718 | // 3. Konturpunkte |
| 1719 | lines.push(''); |
| 1720 | lines.push('KONTURPUNKTE'); |
| 1721 | lines.push(hr44m); |
| 1722 | document.querySelectorAll('details').forEach(det => { |
| 1723 | const sum = det.querySelector('summary'); |
| 1724 | if(!sum) return; |
| 1725 | const title = sum.textContent.trim(); |
| 1726 | if(title.includes('VT') || title.includes('RT')) { |
| 1727 | lines.push(''); |
| 1728 | lines.push(title.toUpperCase()); |
| 1729 | det.querySelectorAll('div').forEach(d => { |
| 1730 | const t = d.textContent.trim(); |
| 1731 | if(t) lines.push(' ' + t); |
| 1732 | }); |
| 1733 | } |
| 1734 | }); |
| 1735 | |
| 1736 | const blob = new Blob([lines.join('\n')], {type:'text/plain;charset=utf-8'}); |
| 1737 | const a = document.createElement('a'); |
| 1738 | a.download = 'autocons_konstruktion.txt'; |
| 1739 | a.href = URL.createObjectURL(blob); |
| 1740 | a.click(); |
| 1741 | } |
| 1742 | |
| 1743 | // ================================================================ |
| 1744 | // A4-Druck: Zeichnung auf mehrere A4-Seiten aufteilen (Maßstab 1:5) |
| 1745 | // ================================================================ |
| 1746 | function printA4() { |
| 1747 | // SVG holen |
| 1748 | const svg = document.getElementById('mainsvg'); |
| 1749 | const svgW = parseFloat(svg.getAttribute('width')); |
| 1750 | const svgH = parseFloat(svg.getAttribute('height')); |
| 1751 | |
| 1752 | // A4 bei 96dpi: 794 x 1123 px (Hochformat) |
| 1753 | // Maßstab 1:5 → 1cm im Schnitt = 5cm auf Papier = ~19px bei 96dpi |
| 1754 | // Wir drucken im Maßstab 1:1 auf das Papier und der Browser skaliert |
| 1755 | // Einfacher Ansatz: SVG auf A4-Seiten aufteilen mit Überlappung |
| 1756 | |
| 1757 | const A4W = 190; // mm nutzbare Breite (A4 - Ränder) |
| 1758 | const A4H = 277; // mm nutzbare Höhe |
| 1759 | const OVERLAP = 10; // mm Überlappung zum Zusammenkleben |
| 1760 | const SCALE = 5; // 1:5 Maßstab (1mm im SVG = 5mm auf Papier) |
| 1761 | |
| 1762 | // SVG-Größe in mm (bei aktuellem Skalierungsfaktor) |
| 1763 | // Der SVG-Maßstab ist in autocons.php: scale px/cm |
| 1764 | // Wir lesen die CFG-Daten |
| 1765 | const scale_px_per_cm = typeof CFG !== 'undefined' ? CFG.scale : 8; |
| 1766 | const svgW_cm = (svgW - 2*44) / scale_px_per_cm; // ca. Breite in cm |
| 1767 | const svgH_cm = (svgH - 2*44) / scale_px_per_cm; |
| 1768 | |
| 1769 | // Seiten berechnen |
| 1770 | const pageW_cm = A4W / 10 / (SCALE/10); // cm auf Papier → cm im Modell |
| 1771 | const pageH_cm = A4H / 10 / (SCALE/10); |
| 1772 | |
| 1773 | // SVG als PNG rendern, dann auf A4-Seiten aufteilen |
| 1774 | const canvas = document.createElement('canvas'); |
| 1775 | // Hohe Auflösung für Druck: 300dpi entspricht ~3.7px/mm |
| 1776 | const DPI = 150; |
| 1777 | const PX_PER_MM = DPI / 25.4; |
| 1778 | canvas.width = Math.round(svgW_cm * 10 * SCALE * PX_PER_MM); |
| 1779 | canvas.height = Math.round(svgH_cm * 10 * SCALE * PX_PER_MM); |
| 1780 | const ctx = canvas.getContext('2d'); |
| 1781 | ctx.fillStyle = '#ffffff'; |
| 1782 | ctx.fillRect(0, 0, canvas.width, canvas.height); |
| 1783 | |
| 1784 | const svgData = new XMLSerializer().serializeToString(svg); |
| 1785 | const blob = new Blob([svgData], {type:'image/svg+xml'}); |
| 1786 | const url = URL.createObjectURL(blob); |
| 1787 | const img = new Image(); |
| 1788 | |
| 1789 | img.onload = function() { |
| 1790 | // SVG auf großes Canvas zeichnen |
| 1791 | ctx.drawImage(img, 0, 0, canvas.width, canvas.height); |
| 1792 | URL.revokeObjectURL(url); |
| 1793 | |
| 1794 | // A4-Seiten in Pixel |
| 1795 | const pageW_px = Math.round(A4W * PX_PER_MM); |
| 1796 | const pageH_px = Math.round(A4H * PX_PER_MM); |
| 1797 | const overlapPx = Math.round(OVERLAP * PX_PER_MM); |
| 1798 | |
| 1799 | const cols = Math.ceil(canvas.width / (pageW_px - overlapPx)); |
| 1800 | const rows = Math.ceil(canvas.height / (pageH_px - overlapPx)); |
| 1801 | |
| 1802 | // Druckfenster öffnen |
| 1803 | const win = window.open('', '_blank', 'width=800,height=600'); |
| 1804 | win.document.write(`<!DOCTYPE html><html><head> |
| 1805 | <title>AutoCons – Maßstabsdruck 1:${SCALE}</title> |
| 1806 | <style> |
| 1807 | @page { size: A4 portrait; margin: 10mm; } |
| 1808 | * { box-sizing: border-box; margin: 0; padding: 0; } |
| 1809 | body { font-family: monospace; font-size: 10px; background: #fff; } |
| 1810 | .page { width: 190mm; height: 277mm; overflow: hidden; |
| 1811 | page-break-after: always; position: relative; |
| 1812 | border: 0.5px solid #ccc; } |
| 1813 | .page:last-child { page-break-after: avoid; } |
| 1814 | .page img { position: absolute; } |
| 1815 | .page-info { position: absolute; bottom: 2mm; right: 2mm; |
| 1816 | font-size: 8px; color: #999; z-index: 10; } |
| 1817 | .crosshair { position: absolute; color: #aaa; font-size: 16px; |
| 1818 | z-index: 10; } |
| 1819 | h1 { font-size: 11px; padding: 2mm; color: #444; } |
| 1820 | .legend { font-size: 9px; padding: 2mm; color: #666; |
| 1821 | border-bottom: 0.5px solid #ddd; margin-bottom: 2mm; } |
| 1822 | @media print { |
| 1823 | .no-print { display: none; } |
| 1824 | .page { border: none; } |
| 1825 | } |
| 1826 | </style> |
| 1827 | </head><body>`); |
| 1828 | |
| 1829 | // Titelseite |
| 1830 | win.document.write(`<div style="padding:10mm"> |
| 1831 | <h1 style="font-size:14px;font-family:serif;margin-bottom:5mm"> |
| 1832 | AutoCons – Optikon Oberteil |
| 1833 | </h1> |
| 1834 | <p style="font-size:10px;color:#666;line-height:1.8"> |
| 1835 | Maßstab: 1:${SCALE}<br> |
| 1836 | Seiten: ${cols} × ${rows} = ${cols*rows} Blatt A4<br> |
| 1837 | Überlappung: ${OVERLAP} mm<br> |
| 1838 | Datum: ${new Date().toLocaleDateString('de-DE')} |
| 1839 | </p> |
| 1840 | <p style="margin-top:5mm;font-size:9px;color:#999"> |
| 1841 | Seiten von links nach rechts, dann von oben nach unten zusammenkleben.<br> |
| 1842 | Überlappungsmarken (†) an den Rändern als Orientierung. |
| 1843 | </p> |
| 1844 | </div>`); |
| 1845 | |
| 1846 | // Eine Canvas-Seite pro A4 |
| 1847 | for(let row=0; row<rows; row++){ |
| 1848 | for(let col=0; col<cols; col++){ |
| 1849 | const srcX = col * (pageW_px - overlapPx); |
| 1850 | const srcY = row * (pageH_px - overlapPx); |
| 1851 | |
| 1852 | // Teilbereich auf kleines Canvas |
| 1853 | const pageCanvas = document.createElement('canvas'); |
| 1854 | pageCanvas.width = pageW_px; |
| 1855 | pageCanvas.height = pageH_px; |
| 1856 | const pctx = pageCanvas.getContext('2d'); |
| 1857 | pctx.fillStyle = '#ffffff'; |
| 1858 | pctx.fillRect(0, 0, pageW_px, pageH_px); |
| 1859 | pctx.drawImage(canvas, srcX, srcY, pageW_px, pageH_px, 0, 0, pageW_px, pageH_px); |
| 1860 | |
| 1861 | // Schnittmarken zeichnen |
| 1862 | pctx.strokeStyle = '#cccccc'; |
| 1863 | pctx.lineWidth = 0.5; |
| 1864 | // Rechter Rand |
| 1865 | if(col < cols-1){ |
| 1866 | pctx.beginPath(); |
| 1867 | pctx.moveTo(pageW_px - overlapPx, 0); |
| 1868 | pctx.lineTo(pageW_px - overlapPx, pageH_px); |
| 1869 | pctx.setLineDash([3,3]); |
| 1870 | pctx.stroke(); |
| 1871 | pctx.setLineDash([]); |
| 1872 | } |
| 1873 | // Unterer Rand |
| 1874 | if(row < rows-1){ |
| 1875 | pctx.beginPath(); |
| 1876 | pctx.moveTo(0, pageH_px - overlapPx); |
| 1877 | pctx.lineTo(pageW_px, pageH_px - overlapPx); |
| 1878 | pctx.setLineDash([3,3]); |
| 1879 | pctx.stroke(); |
| 1880 | pctx.setLineDash([]); |
| 1881 | } |
| 1882 | |
| 1883 | const dataUrl = pageCanvas.toDataURL('image/png'); |
| 1884 | win.document.write(` |
| 1885 | <div class="page"> |
| 1886 | <img src="${dataUrl}" width="190mm" height="277mm" style="top:0;left:0"> |
| 1887 | <div class="page-info">Spalte ${col+1}/${cols} · Zeile ${row+1}/${rows}</div> |
| 1888 | </div>`); |
| 1889 | } |
| 1890 | } |
| 1891 | |
| 1892 | win.document.write('</body></html>'); |
| 1893 | win.document.close(); |
| 1894 | setTimeout(() => win.print(), 800); |
| 1895 | }; |
| 1896 | img.src = url; |
| 1897 | } |
| 1898 | </script> |
| 1899 | |
| 1900 | </body> |
| 1901 | </html> |
| 1902 | |