📜 AutoCons – PHP-Quellcode

1902 Zeilen  Â·  76,057 Bytes← Zurück⬇ Download autocons.php
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) ----
15if (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 &nbsp;·&nbsp; $size Bytes</span>";
54 echo '<a class="back" href="?">&#8592; 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('/&quot;([^&]*)&quot;/','<span style="color:#a09060">&quot;$1&quot;</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
98function 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// ============================================================
120function sqr($a) { return $a * $a; }
121function logMsg($msg) { global $log; $log[] = $msg; }
122
123// ============================================================
124// Geometrische Grundfunktionen (aus grafproc.prg)
125// ============================================================
126function AX($p1,$p2){global $P_;return abs($P_[$p1][0]-$P_[$p2][0]);}
127function AY($p1,$p2){global $P_;return abs($P_[$p1][1]-$P_[$p2][1]);}
128function AD($p1,$p2){global $P_;return sqrt(sqr($P_[$p2][0]-$P_[$p1][0])+sqr($P_[$p2][1]-$P_[$p1][1]));}
129function SignR($r){return ($r>=0)?1.0:-1.0;}
130
131function 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}
138function 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}
146function 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}
152function 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}
161function 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}
167function 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}
176function 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}
183function 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}
191function 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}
199function 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}
208function 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}
219function 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}
231function 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}
238function 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}
245function 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}
251function 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}
268function 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}
282function 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;
321if (($_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// ============================================================
342logMsg("=== VORDERTEIL (VT) ==="); $currentPart = 'VT';
343define('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_;
354PW($A,0.0,0.0);
355$bVo=(10*$uBr_/5+5+3*10*$zBr/20)/10;
356PPW($B,$A,$bVo,0.0);
357PPW($C,$B,0.0,-($hTa-$hBr));
358PW($D,$P_[$A][0],$P_[$C][1]);
359$hVo=$lVo_-$lTV_;
360PPW($E,$D,0.0,$hVo);
361$bHlv=$uHa_/6+$zBr/40;
362PPW($F,$E,$bHlv,0.0);
363PPW($G,$A,(10*$uBr_/10+2+10*$zBr/20)/10,0.0);
364$betaBA=10*$uBr_/40-5.4-abs(10*$uBr_-1040)/200;
365PD($H,$F,$G,$betaBA,'W',' ');
366$l1=(3*10*$uBr_/40+140)/10;
367PPW($I,$B,0.0,$hBr-($l1+$hAV));
368$hAVv=$uBr_/20+0.1+$zBr/20;$bAlv=$hAVv;
369PPW($J,$I,0.0,$hAVv);
370PPW($K,$I,$bAlv,0.0);
371PPW($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;
374PPW(1,$H,$bSuv,0.0);
375PD($M,1,$H,$betaSuv,'W',' ');
376$hHlv=$uHa_/3-4.4+$zBr/40;
377PPW($N,$E,0.0,-$hHlv);
378PPW($O,$N,0.0,$bHlv);
379PPW($PPKT,$O,$bHlv,0.0);
380PW($P1,$P_[$K][0],$P_[$D][1]);
381PPW($P3,$D,0.0,-$lGe_);
382PW($P12,$P_[$G][0],$P_[$P3][1]);
383PW($P2,$P_[$P1][0],$P_[$P3][1]);
384PPW($P15,$P2,1.0,0.0);
385PPW($P13,$P12,0.0,$lGe_/2);
386PW($P11,$P_[$G][0],$P_[$D][1]);
387PPW($P11i,$P11,-$bTAv/2,0.0);
388PPW($P11a,$P11,+$bTAv/2,0.0);
389PPW($P14,$P1,-1.0,0.0);
390LPP(1,$P14,$P15);
391PPW($P16,$D,0.0,$hSaT);
392PPW(1,$P1,0.0,$hSaT);
393LPP(2,$P16,1);
394LPP(3,$P13,$P11a);
395LPP(4,$P13,$P11i);
396PLL($P17,2,4);
397PLL($P18,2,3);
398PLL($P19,1,2);
399PPW($P6,$N,0.0,$VHtv);
400PD($P9,$P6,$O,-45.0,'W',' ');
401PPW($P20,$P6,-$bUb,0.0);
402PPW($P21,$A,-$bUb,0.0);
403PPW($P22,$D,-$bUb,0.0);
404PPW($P23,$P16,-$bUb,0.0);
405$l1=$bSuv/2;
406PZP($P4,$H,$M,$l1/AD($H,$M)*100);
407KR(1,$G,AD($G,$P4));
408KR(2,$F,$bSuv/2);
409PKK(1,1,2,1);PKK(2,1,2,2);
410if($P_[1][0]>$P_[2][0])PPW($P5,1,0.0,0.0);else PPW($P5,2,0.0,0.0);
411PZP($P7,$F,$P5,$VHh/AD($F,$P5)*100);
412PZP($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// ============================================================
440logMsg("=== 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;
455PW($RA,0.0,0.0);
456PPW($RB,$RA,0.0,$hBr);
457PPW($RC,$RB,0.0,-$hTa);
458$bRAo=1.3;
459PPW($RD,$RC,-$bRAo,0.0);
460LPP($LMih,$RD,$RB);
461$bHlh=(10*$uHa_/6+8+10*$zBr/40)/10;
462PPW($RE,$RB,-$bHlh,0.0);
463$hHlh=(10*$uHa_/6-39+3*10*$zBr/200)/10;
464PPW($RF,$RE,0.0,$hHlh);
465LPP(1,$RA,$RB);
466LSP(2,$RA,1);
467PLL($RG,$LMih,2);
468$bRue=(10*$uBr_/8+56+10*$zBr/8)/10;
469PPW($RH,$RG,-$bRue,0.0);
470PW($RI,$P_[$RH][0],$P_[$RB][1]-$li);
471PPW($RJ,$RI,0.0,(10*$uBr_/20-6+10*$zBr/20)/10);
472$bAlh=(10*$uBr_/8-62+7*10*$zBr/40)/10;
473PPW($RK,$RI,-$bAlh,0.0);
474PPW($RL,$RJ,-6/10,0.0);
475$bSuh=(10*$lSu_+7*10*$uBr_/80-57.5+10*$zBr/10)/10;
476PPW(1,$RF,-$bSuh,0.0);
477$betaSuh=$alfaSu_-4-2*10*$hPo/5;$wi=-$betaSuh;
478PD($RM,1,$RF,$wi,'W',' ');
479$hAVh=(10*$uBr_/20+43+10*$zBr/20)/10;
480PPW($RN,$RI,0.0,$hAVh);
481$betaHl=2*$alfaSu_-7;$wi=-$betaHl;
482$bHlv_rt=(10*$uHa_/6+10*$zBr/40)/10;
483PPW(1,$RF,$bHlv_rt,0.0);
484PD($RO,1,$RF,$wi,'W',' ');
485PW($RP1,$P_[$RK][0],$P_[$RC][1]);
486PW($RP2,$P_[$RK][0],$P_[$RC][1]-$lGe_);
487PPW(1,$RC,0.0,-$lGe_);
488LPP(3,$RP2,1);
489PLL($RP3,$LMih,3);
490$l1_rt=AD($RF,$RM);
491PZP($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;
495PZP($RP5,$RF,$RM,$l2_rt/$l1_rt*100);
496PW($RP6,$P_[$RG][0]-$bRue/2+1.0,0.0);
497LPP(4,$RP6,$RP5);
498PZP($RP7,$RP5,$RP6,9.0/AD($RP5,$RP6)*100);
499KR(1,$RF,$bSuv_rt);
500KR(2,$RP7,AD($RP7,$RP4));
501PKK(1,1,2,1);PKK(2,1,2,2);
502if($P_[1][0]<$P_[2][0])PPW($RP8,1,0.0,0.0);else PPW($RP8,2,0.0,0.0);
503PZP($RP9I,$RF,$RP8,($bSuv_rt/2)/AD($RF,$RP8)*100);
504KR(1,$RP4,$bSuv_rt/2+$bSuM);
505KR(2,$RP7,AD($RP7,$RP9I));
506PKK(1,1,2,1);PKK(2,1,2,2);
507if($P_[1][1]>$P_[2][1])PPW($RP9A,1,0.0,0.0);else PPW($RP9A,2,0.0,0.0);
508PPW($RP10,$RG,-$bRue/2-1.0,0.0);
509LPDP(5,$RP10,$LMih);
510PLL($RP11,3,5);
511$l1_rt=AD($RP10,$RP11);
512PZP($RP12,$RP10,$RP11,2.0/$l1_rt*100);
513PZP($RP13,$RP11,$RP10,($lGe_/4)/$l1_rt*100);
514LPDP(6,$RK,$LMih);
515LPP(7,$RC,$RP1);
516PLL($RP14,6,7);
517PLL($RP15,6,3);
518PPW($RP16,$RP14,1.0,0.0);
519PPW($RP17,$RP15,-1.0,0.0);
520LSP(8,$RP16,$LMih);
521PLL($RP18,5,8);
522PLL($RP19,8,$LMih);
523KR(1,$RP18,$bTAh/2);
524PKL(1,1,8,1);PKL(2,1,8,2);
525if($P_[1][0]>$P_[2][0]){PPW($RP18I,1,0,0);PPW($RP18A,2,0,0);}
526else {PPW($RP18A,1,0,0);PPW($RP18I,2,0,0);}
527LPA(9,-abs($hSaT),8);
528PLL($RP20,$LMih,9);
529LPP(10,$RP13,$RP18I);
530LPP(11,$RP13,$RP18A);
531LPP(12,$RP16,$RP17);
532PLL($RP21,9,10);
533PLL($RP22,9,11);
534PLL($RP23,9,12);
535$l1_rt=AD($RB,$RG);
536PZP($RP24,$RB,$RG,$VHth/$l1_rt*100);
537$l1_rt=AD($RF,$RP9I);
538PZP($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=[];
570foreach($vt_contour as $cp){$nr=$cp['nr'];if($P_[$nr][0]<1e14){$vtXs[]=$P_[$nr][0];$vtYs[]=$P_[$nr][1];}}
571$rtXs=[];$rtYs=[];
572foreach($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// ============================================================
591function toSvgXY($nr,&$P,$sc,$ox,$oy){
592 return [round($P[$nr][0]*$sc+$ox,2), round(-$P[$nr][1]*$sc+$oy,2)];
593}
594function 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}
610function 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}
661function 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}
667function 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// ============================================================
680function 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
734foreach($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 = [];
900foreach ($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
909function ayspline_norm(float $a, float $b): float {
910 return sqrt($a*$a + $b*$b);
911}
912
913function 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
940function 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)
972function 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// ============================================================
1025header('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}
1040body{background:var(--paper);color:var(--ink);font-family:'DM Mono',monospace;font-size:13px}
1041header{background:var(--ink);color:var(--paper);padding:1.2rem 2rem;
1042 display:flex;align-items:baseline;gap:1rem}
1043header h1{font-family:'DM Serif Display',serif;font-size:1.8rem;font-weight:400}
1044header 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}
1049svg.sketch{border:1px solid var(--grid);background:#fffef9;
1050 box-shadow:0 2px 14px rgba(0,0,0,.09);display:block;max-width:100%}
1051aside{background:#ede8df;border-left:1px solid var(--grid);
1052 padding:1.2rem;overflow-y:auto;display:flex;flex-direction:column;gap:1rem}
1053aside 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}
1068details{font-size:.7rem}
1069summary{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 &#9998; Editor v0.5
1143 </a>
1144 <p>Schnittmusterkonstruktion · Vorderteil &amp; 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 -----
1154echo "<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;
1158echo "<g id='grid' stroke='#ece6d8' stroke-width='.4'>\n";
1159for($gx=$margin;$gx<$SVG_W;$gx+=$step)
1160 echo "<line x1='".round($gx)."' y1='0' x2='".round($gx)."' y2='$SVG_H'/>\n";
1161for($gy=0;$gy<$SVG_H;$gy+=$step)
1162 echo "<line x1='0' y1='".round($gy)."' x2='$SVG_W' y2='".round($gy)."'/>\n";
1163echo "</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
1168echo "<g id='steps-layer'>\n";
1169foreach($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}
1174echo "</g>\n";
1175
1176// ---- Kontur-Layer (VT + RT) ---- immer sichtbar im Normalmodus
1177echo "<g id='contour-layer'>\n";
1178
1179// VT interne Linien
1180echo "<g id='vt-internal'>\n";
1181foreach($vt_internal as $il) echo svgIL($il[0],$il[1],$P_,$scale,$vtOx,$Oy,'#7a9abf');
1182echo "</g>\n";
1183
1184// VT Konturpfad
1185echo "<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
1189echo "<g id='vt-labels'>\n";
1190foreach($vt_lbl_map as $nr=>$lbl) echo svgLbl($nr,$lbl,$P_,$scale,$vtOx,$Oy,'#3a5a8a');
1191echo "</g>\n";
1192
1193// VT Wasserzeichen
1194$cx=0;$cy=0;$cnt=0;
1195foreach($vt_contour as $cp){$nr=$cp['nr'];if($P_[$nr][0]<1e14){$cx+=$P_[$nr][0];$cy+=$P_[$nr][1];$cnt++;}}
1196if($cnt){$tx=round($cx/$cnt*$scale+$vtOx);$ty=round(-$cy/$cnt*$scale+$Oy);
1197echo "<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
1201echo "<g id='rt-internal'>\n";
1202foreach($rt_internal as $il) echo svgIL($il[0],$il[1],$P_,$scale,$rtOx,$Oy,'#bf7a7a');
1203echo "</g>\n";
1204
1205// RT Konturpfad
1206echo "<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
1210echo "<g id='rt-labels'>\n";
1211foreach($rt_lbl_map as $nr=>$lbl) echo svgLbl($nr,$lbl,$P_,$scale,$rtOx,$Oy,'#8a3a3a');
1212echo "</g>\n";
1213
1214// RT Wasserzeichen
1215$cx=0;$cy=0;$cnt=0;
1216foreach($rt_contour as $cp){$nr=$cp['nr'];if($P_[$nr][0]<1e14){$cx+=$P_[$nr][0];$cy+=$P_[$nr][1];$cnt++;}}
1217if($cnt){$tx=round($cx/$cnt*$scale+$rtOx);$ty=round(-$cy/$cnt*$scale+$Oy);
1218echo "<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
1221echo "</g>\n"; // contour-layer
1222
1223// Maßstab
1224$barX=$margin;$barY=$SVG_H-14;$barLen=10*$scale;
1225echo "<g stroke='#888' stroke-width='1.5'>\n";
1226echo "<line x1='$barX' y1='".($barY-4)."' x2='$barX' y2='".($barY+4)."'/>\n";
1227echo "<line x1='".round($barX+$barLen)."' y1='".($barY-4)."' x2='".round($barX+$barLen)."' y2='".($barY+4)."'/>\n";
1228echo "<line x1='$barX' y1='$barY' x2='".round($barX+$barLen)."' y2='$barY'/>\n";
1229echo "</g>\n";
1230echo "<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
1233echo "</svg>\n"; // Ende SVG
1234?>
1235
1236
1237
1238</section><!-- main -->
1239
1240<!-- ===== SIDEBAR ===== -->
1241<aside>
1242<?php
1243echo "<div>\n<h3>Maßtabelle</h3>\n";
1244echo "<form method='post' action=''>\n";
1245echo "<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];
1262foreach($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}
1270echo "</div>\n";
1271echo "<button class='btn' type='submit'>&#8635; Konstruiere</button>\n";
1272echo "</form>\n</div>\n";
1273
1274// Player-Checkbox (wird unter das SVG verschoben)
1275$totalSteps=count($svgSteps);
1276
1277// Legende
1278echo "<div class='legend'>\n<h3>Legende</h3>\n";
1279echo "<div><span style='background:#3a5a8a'></span>Vorderteil (VT)</div>\n";
1280echo "<div><span style='background:#8a3a3a'></span>Rückenteil (RT)</div>\n";
1281echo "<div><span style='border-top:1px dashed #c8960a;display:inline-block;width:20px;height:3px'></span>Hilfslinien</div>\n";
1282echo "<div><span style='border-top:1px dashed #2090c0;display:inline-block;width:20px;height:3px'></span>Hilfskreise</div>\n";
1283echo "</div>\n";
1284
1285// Protokoll
1286echo "<details open>\n<summary>Konstruktionsprotokoll</summary>\n";
1287echo "<div class='logbox'>\n";
1288foreach($log as $l) echo htmlspecialchars($l)."<br>\n";
1289echo "</div></details>\n";
1290
1291// Konturpunkte
1292echo "<details>\n<summary>Konturpunkte VT</summary>\n<div class='logbox'>\n";
1293foreach($vt_contour as $cp){
1294 $nr=$cp['nr'];
1295 if($P_[$nr][0]<1e14 && $cp['lbl']!=='')
1296 printf("<div><b>%s</b>&nbsp;x=%.2f&nbsp;y=%.2f%s</div>\n",
1297 $cp['lbl'],$P_[$nr][0],$P_[$nr][1],
1298 $cp['info']?" [{$cp['info']}]":'');
1299}
1300echo "</div></details>\n";
1301
1302echo "<details>\n<summary>Konturpunkte RT</summary>\n<div class='logbox'>\n";
1303foreach($rt_contour as $cp){
1304 $nr=$cp['nr'];
1305 if($P_[$nr][0]<1e14 && $cp['lbl']!=='')
1306 printf("<div><b>%s</b>&nbsp;x=%.2f&nbsp;y=%.2f%s</div>\n",
1307 $cp['lbl'],$P_[$nr][0],$P_[$nr][1],
1308 $cp['info']?" [{$cp['info']}]":'');
1309}
1310echo "</div></details>\n";
1311
1312// ---- Export-Sektion ----
1313echo "<div class='export-section'>\n";
1314echo "<h3>Export &amp; Druck</h3>\n";
1315
1316// Button: Zeichnung als PNG
1317echo "<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
1323echo "<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)
1329echo "<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
1335echo "<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)
1341echo "<button class='exp-btn' onclick='exportData()'>
1342 <span class='icon'>📋</span>
1343 <span class='lbl'>Konstruktionstext<br><span class='hint'>Maße, Schritte &amp; Punkte als Textdatei</span></span>
1344</button>\n";
1345echo "<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
1350echo "<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
1355echo "</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>&#9654; Konstruktionsablauf</span>
1367 </label>
1368 <span style="font-size:.68rem;color:#6a5a3a"><?= $totalSteps ?> Schritte &nbsp;·&nbsp; &#8592;&#8594; Pfeiltasten &nbsp;·&nbsp; Leertaste Play/Pause &nbsp;·&nbsp; Esc Schließen</span>
1369 </div>
1370 <div id="pl-row1">
1371 <button class="pl-btn" id="pl-rew" title="Zum Anfang">&#9664;&#9664;</button>
1372 <button class="pl-btn" id="pl-prev" title="Zurück (←)">&#9664;</button>
1373 <button class="pl-btn" id="pl-play" title="Play/Pause (Leertaste)">&#9654;</button>
1374 <button class="pl-btn" id="pl-next" title="Vorwärts (→)">&#9654;&#9654;</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 = '&#9646;&#9646;';
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 = '&#9654;';
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)
1515echo "<script>\nconst DESCS = [\n";
1516foreach($svgSteps as $i=>$st){
1517 echo " ".json_encode($st['desc'],JSON_UNESCAPED_UNICODE);
1518 echo ($i<count($svgSteps)-1)?",\n":"\n";
1519}
1520echo "];\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
1529function 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
1552function 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
1561function 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
1584function printSketch() {
1585 document.body.classList.remove('print-sidebar');
1586 window.print();
1587}
1588
1589function printSidebar() {
1590 document.body.classList.add('print-sidebar');
1591 window.print();
1592 setTimeout(() => document.body.classList.remove('print-sidebar'), 1200);
1593}
1594
1595function 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(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/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// ================================================================
1746function 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