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>
60 lines
2.8 KiB
JavaScript
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 → Site-Frame · ${(x1 - x0).toFixed(1)} × ${(z1 - z0).toFixed(1)} m · 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`);
|