Files
Gartenvisualisierung/tools/debug-svg.mjs
T
admGitAICDSandClaude Opus 5 70d239fe9c Begehbare 3D-Visualisierung des Gartens Egglihaldenstrasse 9a
Geometrie massstaeblich aus dem Gartenplan-PDF (1:100) extrahiert: Belaege,
Mauern, Pflanzenstandorte und 46 Hoehenkoten. Wohnhaus nach Gartenplan und
den Fassadenplaenen von EHLE Architekten.

- tools/: Pipeline PDF -> Szenenmodell (MuPDF, Node)
- src/: three.js-Szene mit prozeduralen Texturen, Rundgang und Uebersicht
- Docker/Traefik fuer garten.aicds.ch, Gitea-Workflow auf main

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:59:37 +02:00

60 lines
2.8 KiB
JavaScript

/**
* Zeichnet die extrahierte Geometrie als SVG-Draufsicht - Kontrolle, ob die
* Transformation aus dem PDF stimmt.
*
* Aufruf: node tools/debug-svg.mjs [nur-farbe-hex]
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(__dirname, 'out');
const data = JSON.parse(fs.readFileSync(path.join(OUT, 'plan-raw.json'), 'utf8'));
const only = process.argv[2]?.toLowerCase();
const PAD = 1.5;
const PXM = 34; // Pixel pro Meter
let x0 = Infinity, z0 = Infinity, x1 = -Infinity, z1 = -Infinity;
for (const s of data.shapes) for (const [x, z] of s.pts) {
x0 = Math.min(x0, x); x1 = Math.max(x1, x); z0 = Math.min(z0, z); z1 = Math.max(z1, z);
}
x0 -= PAD; z0 -= PAD; x1 += PAD; z1 += PAD;
const W = Math.round((x1 - x0) * PXM), H = Math.round((z1 - z0) * PXM);
const X = (x) => ((x - x0) * PXM).toFixed(1);
const Z = (z) => ((z - z0) * PXM).toFixed(1);
const out = [];
out.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">`);
out.push(`<rect width="${W}" height="${H}" fill="#ffffff"/>`);
const fills = data.shapes.filter((s) => s.op === 'f' && s.area > 0.05);
fills.sort((a, b) => b.area - a.area);
for (const s of fills) {
if (only && s.hex.toLowerCase() !== only) continue;
const d = s.pts.map((p, i) => `${i ? 'L' : 'M'}${X(p[0])},${Z(p[1])}`).join('') + 'Z';
out.push(`<path d="${d}" fill="${s.hex}" fill-opacity="0.85" stroke="#0006" stroke-width="0.4"/>`);
}
if (!only) {
for (const s of data.shapes) {
if (s.op !== 's' || s.pts.length < 2) continue;
const d = s.pts.map((p, i) => `${i ? 'L' : 'M'}${X(p[0])},${Z(p[1])}`).join('');
out.push(`<path d="${d}" fill="none" stroke="#333" stroke-width="0.35" stroke-opacity="0.55"/>`);
}
for (const sp of data.spots) {
out.push(`<circle cx="${X(sp.x)}" cy="${Z(sp.z)}" r="2.4" fill="#d00"/>`);
out.push(`<text x="${X(sp.x) - 0}" y="${+Z(sp.z) - 4}" font-size="9" fill="#a00" text-anchor="middle">${sp.y.toFixed(2)}</text>`);
}
}
// Massstab und Achsen
out.push(`<g stroke="#06c" stroke-width="2" fill="none">
<path d="M${X(0)},${Z(0)} L${X(5)},${Z(0)}"/><path d="M${X(0)},${Z(0)} L${X(0)},${Z(5)}"/></g>`);
out.push(`<text x="${X(5.2)}" y="${Z(0)}" font-size="13" fill="#06c">+X (ENE)</text>`);
out.push(`<text x="${X(0.2)}" y="${Z(5.4)}" font-size="13" fill="#06c">+Z (SSE)</text>`);
out.push(`<text x="12" y="20" font-size="14" fill="#333">Gartenplan &#8594; Site-Frame &#183; ${(x1 - x0).toFixed(1)} &#215; ${(z1 - z0).toFixed(1)} m &#183; 1 Raster = 1 m</text>`);
out.push('</svg>');
const file = path.join(OUT, only ? `debug-${only.replace('#', '')}.svg` : 'debug-plan.svg');
fs.writeFileSync(file, out.join('\n'));
console.log('geschrieben:', file, `${W}x${H}px`);