DXF-Import: TEXT/MTEXT -> editierbarer Plan-Text (Drawing2D text-Primitiv, SVG-Render)
This commit is contained in:
@@ -779,6 +779,7 @@ export const de = {
|
|||||||
"import.file": "Datei",
|
"import.file": "Datei",
|
||||||
"import.summary.heading": "Inhalt",
|
"import.summary.heading": "Inhalt",
|
||||||
"import.summary.contours": "{n} Konturen",
|
"import.summary.contours": "{n} Konturen",
|
||||||
|
"import.summary.texts": "{n} Texte",
|
||||||
"import.summary.meshes": "{n} Meshes",
|
"import.summary.meshes": "{n} Meshes",
|
||||||
"import.summary.layers": "Layer: {layers}",
|
"import.summary.layers": "Layer: {layers}",
|
||||||
"import.summary.noLayers": "keine Layer-Namen",
|
"import.summary.noLayers": "keine Layer-Namen",
|
||||||
|
|||||||
@@ -768,6 +768,7 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"import.file": "File",
|
"import.file": "File",
|
||||||
"import.summary.heading": "Contents",
|
"import.summary.heading": "Contents",
|
||||||
"import.summary.contours": "{n} contours",
|
"import.summary.contours": "{n} contours",
|
||||||
|
"import.summary.texts": "{n} texts",
|
||||||
"import.summary.meshes": "{n} meshes",
|
"import.summary.meshes": "{n} meshes",
|
||||||
"import.summary.layers": "Layers: {layers}",
|
"import.summary.layers": "Layers: {layers}",
|
||||||
"import.summary.noLayers": "no layer names",
|
"import.summary.noLayers": "no layer names",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { parseDxf } from "./dxfParser";
|
import { parseDxf } from "./dxfParser";
|
||||||
import { contoursToDrawings } from "./dxfToDrawings";
|
import { contoursToDrawings, textsToDrawings } from "./dxfToDrawings";
|
||||||
import type { Contour, ContourSet, Vec2 } from "../model/types";
|
import type { Contour, ContourSet, Vec2 } from "../model/types";
|
||||||
|
|
||||||
/** Minimales DXF aus Gruppencode/Wert-Paaren; nur eine ENTITIES-Sektion. */
|
/** Minimales DXF aus Gruppencode/Wert-Paaren; nur eine ENTITIES-Sektion. */
|
||||||
@@ -411,6 +411,66 @@ describe("parseDxf — HATCH", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── TEXT / MTEXT ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** TEXT-Entity: Position, Höhe, Rotation (Grad), String. */
|
||||||
|
function textEnt(x: number, y: number, h: number, rotDeg: number, s: string): string[] {
|
||||||
|
return ["0", "TEXT", "8", "0", "10", `${x}`, "20", `${y}`, "30", "0", "40", `${h}`, "50", `${rotDeg}`, "1", s];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** MTEXT-Entity: Position, Höhe, String (Rotation 0). */
|
||||||
|
function mtextEnt(x: number, y: number, h: number, s: string): string[] {
|
||||||
|
return ["0", "MTEXT", "8", "0", "10", `${x}`, "20", `${y}`, "30", "0", "40", `${h}`, "50", "0", "1", s];
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("parseDxf — TEXT/MTEXT", () => {
|
||||||
|
it("TEXT → ImportedText mit Position/Höhe/Winkel (Grad→Radiant)", () => {
|
||||||
|
const res = parseDxf(dxf(textEnt(5, 3, 0.5, 90, "Hallo")));
|
||||||
|
expect(res.texts).toHaveLength(1);
|
||||||
|
const t0 = res.texts![0];
|
||||||
|
expect(t0.at.x).toBeCloseTo(5, 9);
|
||||||
|
expect(t0.at.y).toBeCloseTo(3, 9);
|
||||||
|
expect(t0.height).toBeCloseTo(0.5, 9);
|
||||||
|
expect(t0.angle).toBeCloseTo(Math.PI / 2, 9);
|
||||||
|
expect(t0.text).toBe("Hallo");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("MTEXT → Formatcodes gesäubert (\\P → Leerzeichen)", () => {
|
||||||
|
const res = parseDxf(dxf(mtextEnt(1, 2, 0.3, "Welt\\Pzeile2")));
|
||||||
|
expect(res.texts![0].text).toBe("Welt zeile2");
|
||||||
|
expect(res.texts![0].at.x).toBeCloseTo(1, 9);
|
||||||
|
expect(res.texts![0].height).toBeCloseTo(0.3, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leerer Text wird übersprungen", () => {
|
||||||
|
const res = parseDxf(dxf(textEnt(0, 0, 0.2, 0, "")));
|
||||||
|
expect(res.texts ?? []).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fehlende Höhe → positiver Default", () => {
|
||||||
|
const g = ["0", "TEXT", "8", "0", "10", "0", "20", "0", "30", "0", "1", "X"];
|
||||||
|
const res = parseDxf(dxf(g));
|
||||||
|
expect(res.texts![0].height).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("textsToDrawings", () => {
|
||||||
|
it("ImportedText → Drawing2D {shape:\"text\"}", () => {
|
||||||
|
const drawings = textsToDrawings(
|
||||||
|
[{ at: { x: 1, y: 2 }, text: "Hi", height: 0.4, angle: 0, layer: "L" }],
|
||||||
|
"lvl", "active", "CODE", (s) => s,
|
||||||
|
);
|
||||||
|
expect(drawings).toHaveLength(1);
|
||||||
|
const g = drawings[0].geom;
|
||||||
|
expect(g.shape).toBe("text");
|
||||||
|
if (g.shape === "text") {
|
||||||
|
expect(g.text).toBe("Hi");
|
||||||
|
expect(g.height).toBeCloseTo(0.4, 9);
|
||||||
|
expect(g.at.x).toBeCloseTo(1, 9);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("contoursToDrawings — HATCH-Füllung", () => {
|
describe("contoursToDrawings — HATCH-Füllung", () => {
|
||||||
it("gefüllte Kontur → Drawing2D mit fillColor und polyline-Form", () => {
|
it("gefüllte Kontur → Drawing2D mit fillColor und polyline-Form", () => {
|
||||||
const set: ContourSet = {
|
const set: ContourSet = {
|
||||||
|
|||||||
+54
-3
@@ -24,9 +24,10 @@
|
|||||||
// • INSERT → Block-Konturen, transformiert (Scale/Rot/Array, verschachtelt).
|
// • INSERT → Block-Konturen, transformiert (Scale/Rot/Array, verschachtelt).
|
||||||
// • HATCH → gefüllte Kontur(en) je Randpfad (Custom-Handler,
|
// • HATCH → gefüllte Kontur(en) je Randpfad (Custom-Handler,
|
||||||
// Polyline- + Linien-/Bogen-/Ellipsen-/Spline-Kanten; Fill via `Contour.filled`).
|
// Polyline- + Linien-/Bogen-/Ellipsen-/Spline-Kanten; Fill via `Contour.filled`).
|
||||||
|
// • TEXT / MTEXT → ImportedText (Position/Höhe/Winkel) → Drawing2D `{shape:"text"}`.
|
||||||
|
|
||||||
import DxfParser from "dxf-parser";
|
import DxfParser from "dxf-parser";
|
||||||
import type { Contour, ContourSet, ImportedMesh, Vec2 } from "../model/types";
|
import type { Contour, ContourSet, ImportedMesh, ImportedText, Vec2 } from "../model/types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optionale Import-Diagnose: Histogramm der gelesenen Entity-Typen (Typ → Anzahl)
|
* Optionale Import-Diagnose: Histogramm der gelesenen Entity-Typen (Typ → Anzahl)
|
||||||
@@ -41,10 +42,12 @@ export interface ImportDiagnostics {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ergebnis eines DXF-Imports: rohe Meshes + Konturen-Sätze. */
|
/** Ergebnis eines DXF-Imports: rohe Meshes + Konturen-Sätze + Texte. */
|
||||||
export interface DxfImportResult {
|
export interface DxfImportResult {
|
||||||
meshes: ImportedMesh[];
|
meshes: ImportedMesh[];
|
||||||
contours: ContourSet[];
|
contours: ContourSet[];
|
||||||
|
/** Importierte TEXT/MTEXT-Elemente (optional; der DWG-Parser füllt sie nicht). */
|
||||||
|
texts?: ImportedText[];
|
||||||
/** Optionale Diagnose (Entity-Typ-Histogramm); vom DWG-Parser gefüllt. */
|
/** Optionale Diagnose (Entity-Typ-Histogramm); vom DWG-Parser gefüllt. */
|
||||||
diagnostics?: ImportDiagnostics;
|
diagnostics?: ImportDiagnostics;
|
||||||
}
|
}
|
||||||
@@ -97,6 +100,12 @@ interface DxfEntity {
|
|||||||
rowSpacing?: number;
|
rowSpacing?: number;
|
||||||
// HATCH: rohe Gruppencodes (vom Custom-Handler gesammelt, siehe HatchHandler).
|
// HATCH: rohe Gruppencodes (vom Custom-Handler gesammelt, siehe HatchHandler).
|
||||||
rawCodes?: RawCode[];
|
rawCodes?: RawCode[];
|
||||||
|
// TEXT/MTEXT. `position` (MTEXT-Einfügepunkt) wird tolerant gelesen (oben als
|
||||||
|
// MESH-Vertexliste getippt). `rotation` (Grad) ist bereits oben deklariert.
|
||||||
|
startPoint?: DxfVertex; // TEXT-Einfügepunkt
|
||||||
|
textHeight?: number; // TEXT-Höhe (Code 40)
|
||||||
|
height?: number; // MTEXT-Höhe (Code 40)
|
||||||
|
text?: string; // Textinhalt (Code 1, MTEXT auch 3)
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
}
|
}
|
||||||
/** Ein rohes DXF-Gruppencode/Wert-Paar (HATCH-Randpfad-Auswertung). */
|
/** Ein rohes DXF-Gruppencode/Wert-Paar (HATCH-Randpfad-Auswertung). */
|
||||||
@@ -140,6 +149,7 @@ export function parseDxf(text: string): DxfImportResult {
|
|||||||
const meshTriangles: number[] = []; // gesammelte Mesh-Positions (x,y,z…)
|
const meshTriangles: number[] = []; // gesammelte Mesh-Positions (x,y,z…)
|
||||||
const meshIndices: number[] = [];
|
const meshIndices: number[] = [];
|
||||||
const contours: Contour[] = [];
|
const contours: Contour[] = [];
|
||||||
|
const texts: ImportedText[] = [];
|
||||||
|
|
||||||
for (const e of entities) {
|
for (const e of entities) {
|
||||||
const type = (e.type ?? "").toUpperCase();
|
const type = (e.type ?? "").toUpperCase();
|
||||||
@@ -156,6 +166,11 @@ export function parseDxf(text: string): DxfImportResult {
|
|||||||
addPolyfaceMesh(meshTriangles, meshIndices, e);
|
addPolyfaceMesh(meshTriangles, meshIndices, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (type === "TEXT" || type === "MTEXT") {
|
||||||
|
const it = textEntity(e, type);
|
||||||
|
if (it) texts.push(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Alle übrigen Kontur-Entities (inkl. SPLINE + INSERT-Block-Expansion) über
|
// Alle übrigen Kontur-Entities (inkl. SPLINE + INSERT-Block-Expansion) über
|
||||||
// den gemeinsamen Sammler — dieselbe Logik nutzt die INSERT-Rekursion.
|
// den gemeinsamen Sammler — dieselbe Logik nutzt die INSERT-Rekursion.
|
||||||
collectContours(e, blocks, contours, 0);
|
collectContours(e, blocks, contours, 0);
|
||||||
@@ -182,7 +197,43 @@ export function parseDxf(text: string): DxfImportResult {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { meshes, contours: contourSets };
|
return { meshes, contours: contourSets, texts };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── TEXT / MTEXT ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Fallback-Schrifthöhe (Meter), wenn die DXF-Höhe fehlt/0 ist. */
|
||||||
|
const DEFAULT_TEXT_HEIGHT = 0.25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MTEXT-Inhalt von Formatierungscodes säubern (grober erster Wurf): \P → Leer-
|
||||||
|
* zeichen, \~ → Leerzeichen, `\f…;`/`\H…;`/`\C…;`/`\A…;`-Sequenzen und die {}-
|
||||||
|
* Gruppenklammern entfernen, `\{`/`\}`/`\\` entschärfen. Komplexe Verschachte-
|
||||||
|
* lungen werden flachgeklopft (dokumentierte Grenze).
|
||||||
|
*/
|
||||||
|
function sanitizeMText(s: string): string {
|
||||||
|
return s
|
||||||
|
.replace(/\\P/gi, " ")
|
||||||
|
.replace(/\\~/g, " ")
|
||||||
|
.replace(/\\[A-Za-z][^;\\]*;/g, "")
|
||||||
|
.replace(/[{}]/g, "")
|
||||||
|
.replace(/\\([{}\\])/g, "$1")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** TEXT/MTEXT-Entity → ImportedText (Position/Höhe/Winkel), oder null bei leer. */
|
||||||
|
function textEntity(e: DxfEntity, type: string): ImportedText | null {
|
||||||
|
const rawText = typeof e.text === "string" ? e.text : "";
|
||||||
|
const content = type === "MTEXT" ? sanitizeMText(rawText) : rawText;
|
||||||
|
if (!content) return null;
|
||||||
|
const at: Vec2 =
|
||||||
|
type === "MTEXT"
|
||||||
|
? { x: coord(e.position, "x"), y: coord(e.position, "y") }
|
||||||
|
: { x: e.startPoint?.x ?? 0, y: e.startPoint?.y ?? 0 };
|
||||||
|
const rawHeight = type === "MTEXT" ? numOr(e.height, 0) : numOr(e.textHeight, 0);
|
||||||
|
const height = rawHeight > 0 ? rawHeight : DEFAULT_TEXT_HEIGHT;
|
||||||
|
const angle = (numOr(e.rotation, 0) * Math.PI) / 180; // DXF: Grad CCW → Radiant
|
||||||
|
return { at, text: content, height, angle, layer: e.layer };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Mesh-Entities ────────────────────────────────────────────────────────────
|
// ── Mesh-Entities ────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+35
-1
@@ -15,7 +15,7 @@
|
|||||||
//
|
//
|
||||||
// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
|
// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
|
||||||
|
|
||||||
import type { Contour, ContourSet, Drawing2D, Drawing2DGeom } from "../model/types";
|
import type { Contour, ContourSet, Drawing2D, Drawing2DGeom, ImportedText } from "../model/types";
|
||||||
|
|
||||||
/** Wie DXF-Layer auf Kategorien abgebildet werden. */
|
/** Wie DXF-Layer auf Kategorien abgebildet werden. */
|
||||||
export type CategoryMode = "active" | "byLayer";
|
export type CategoryMode = "active" | "byLayer";
|
||||||
@@ -66,6 +66,40 @@ export function contoursToDrawings(
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wandelt importierte Texte in Drawing2D-`text`-Elemente um — analog
|
||||||
|
* `contoursToDrawings` (gleiche Layer→Kategorie-Zuordnung). Winkel/Höhe kommen
|
||||||
|
* 1:1 aus `ImportedText` (Höhe in Modell-Metern, Winkel in Radiant).
|
||||||
|
*/
|
||||||
|
export function textsToDrawings(
|
||||||
|
texts: ImportedText[],
|
||||||
|
levelId: string,
|
||||||
|
categoryMode: CategoryMode,
|
||||||
|
fallbackCode: string,
|
||||||
|
layerToCode: (layerName: string) => string,
|
||||||
|
): Drawing2D[] {
|
||||||
|
const out: Drawing2D[] = [];
|
||||||
|
for (const it of texts) {
|
||||||
|
const layerName = it.layer ?? "";
|
||||||
|
const categoryCode =
|
||||||
|
categoryMode === "byLayer" && layerName ? layerToCode(layerName) : fallbackCode;
|
||||||
|
out.push({
|
||||||
|
id: nextId(),
|
||||||
|
type: "drawing2d",
|
||||||
|
levelId,
|
||||||
|
categoryCode,
|
||||||
|
geom: {
|
||||||
|
shape: "text",
|
||||||
|
at: { ...it.at },
|
||||||
|
text: it.text,
|
||||||
|
height: it.height,
|
||||||
|
angle: it.angle,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/** Eine Kontur → 2D-Geometrie (line bei 2 offenen Punkten, sonst polyline). */
|
/** Eine Kontur → 2D-Geometrie (line bei 2 offenen Punkten, sonst polyline). */
|
||||||
function contourGeom(contour: Contour): Drawing2DGeom | null {
|
function contourGeom(contour: Contour): Drawing2DGeom | null {
|
||||||
const pts = contour.pts;
|
const pts = contour.pts;
|
||||||
|
|||||||
@@ -1100,6 +1100,20 @@ export interface Contour {
|
|||||||
filled?: boolean;
|
filled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein importiertes Text-Element (DXF TEXT/MTEXT). `at` = Einfügepunkt in Metern,
|
||||||
|
* `height` = Schrifthöhe in Modell-Metern, `angle` = Drehung in RADIANT (CCW).
|
||||||
|
* Wird beim Drawing-Import zu einem `{shape:"text"}`-Drawing2D.
|
||||||
|
*/
|
||||||
|
export interface ImportedText {
|
||||||
|
at: Vec2;
|
||||||
|
text: string;
|
||||||
|
height: number;
|
||||||
|
angle: number;
|
||||||
|
/** Ursprünglicher DXF-Layer-Name (für die Kategorisierung beim 2D-Import). */
|
||||||
|
layer?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/** Ein Satz Konturen (z. B. alle Höhenlinien eines DXF-Imports). */
|
/** Ein Satz Konturen (z. B. alle Höhenlinien eines DXF-Imports). */
|
||||||
export interface ContourSet {
|
export interface ContourSet {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
+32
-1
@@ -1640,7 +1640,15 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
|
|||||||
// Raum-Stempel selbst (glyphon) — das SVG darf ihn NICHT zusätzlich
|
// Raum-Stempel selbst (glyphon) — das SVG darf ihn NICHT zusätzlich
|
||||||
// zeichnen (sonst doppelter Text), also entfällt dort auch das Textoverlay.
|
// zeichnen (sonst doppelter Text), also entfällt dort auch das Textoverlay.
|
||||||
// Sonst (reines SVG): alles außer den gebündelten 2D-Zeichenlinien.
|
// Sonst (reines SVG): alles außer den gebündelten 2D-Zeichenlinien.
|
||||||
(useGpuRenderer ? wantWasm || p.kind !== "text" : p.kind === "line" && p.drawingId) ? null : (
|
// `drawingText` (importierter Einzeltext) rendert IMMER im SVG — der GL-
|
||||||
|
// wie der WASM-Renderer zeichnet ihn nicht. Der Raum-Stempel (`text`)
|
||||||
|
// bleibt: unter WASM rastert ihn render2d selbst (sonst doppelt).
|
||||||
|
(useGpuRenderer
|
||||||
|
? wantWasm
|
||||||
|
? p.kind !== "drawingText"
|
||||||
|
: p.kind !== "text" && p.kind !== "drawingText"
|
||||||
|
: p.kind === "line" && p.drawingId)
|
||||||
|
? null : (
|
||||||
<PrimitiveShape
|
<PrimitiveShape
|
||||||
key={i}
|
key={i}
|
||||||
p={p}
|
p={p}
|
||||||
@@ -3065,5 +3073,28 @@ function renderPrimitive(
|
|||||||
</text>
|
</text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case "drawingText": {
|
||||||
|
// Modellverankerter Einzeltext (DXF-Import): Höhe in Metern → viewBox-
|
||||||
|
// Einheiten (skaliert mit dem Zoom), Baseline am Ankerpunkt (links).
|
||||||
|
const c = toScreen(p.at);
|
||||||
|
const fontSize = p.heightM * PX_PER_M;
|
||||||
|
// Modell-Winkel CCW; Screen-Y ist gespiegelt und SVG-rotate CW-positiv → -deg.
|
||||||
|
const deg = -(p.angle * 180) / Math.PI;
|
||||||
|
return (
|
||||||
|
<text
|
||||||
|
x={c.x}
|
||||||
|
y={c.y}
|
||||||
|
fontSize={fontSize}
|
||||||
|
fontFamily="var(--font-ui, sans-serif)"
|
||||||
|
fill={p.color}
|
||||||
|
opacity={p.greyed ? 0.45 : undefined}
|
||||||
|
textAnchor="start"
|
||||||
|
transform={deg ? `rotate(${deg} ${c.x} ${c.y})` : undefined}
|
||||||
|
style={{ pointerEvents: "none", userSelect: "none" }}
|
||||||
|
>
|
||||||
|
{p.text}
|
||||||
|
</text>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,6 +294,23 @@ export type Primitive =
|
|||||||
/** ID des Raums (Room), zu dem dieser Stempel gehört (Auswahl). */
|
/** ID des Raums (Room), zu dem dieser Stempel gehört (Auswahl). */
|
||||||
roomId?: string;
|
roomId?: string;
|
||||||
greyed?: boolean;
|
greyed?: boolean;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
// Schlichter, modellverankerter Einzeltext (aus Drawing2D `{shape:"text"}`,
|
||||||
|
// z. B. DXF-Import). Anders als `kind:"text"` (Raum-Stempel, RichText) trägt
|
||||||
|
// er nur einen String mit Höhe in Metern + Drehung — rein SVG-gerendert.
|
||||||
|
kind: "drawingText";
|
||||||
|
/** Ankerpunkt im Modell (Meter). */
|
||||||
|
at: Vec2;
|
||||||
|
text: string;
|
||||||
|
/** Schrifthöhe in Modell-Metern. */
|
||||||
|
heightM: number;
|
||||||
|
/** Drehung in RADIANT (CCW im Modell). */
|
||||||
|
angle: number;
|
||||||
|
color: string;
|
||||||
|
/** ID des 2D-Zeichenelements (für Links-Klick-Auswahl). */
|
||||||
|
drawingId?: string;
|
||||||
|
greyed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1013,7 +1030,19 @@ function addDrawing2D(
|
|||||||
for (let i = 0; i < N; i++) seg(pts[i], pts[(i + 1) % N]);
|
for (let i = 0; i < N; i++) seg(pts[i], pts[(i + 1) % N]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// arc/text: Primitive-Erweiterungen folgen später.
|
case "text":
|
||||||
|
out.push({
|
||||||
|
kind: "drawingText",
|
||||||
|
at: g.at,
|
||||||
|
text: g.text,
|
||||||
|
heightM: g.height,
|
||||||
|
angle: g.angle,
|
||||||
|
color,
|
||||||
|
greyed,
|
||||||
|
drawingId: d.id,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
// arc: Primitive-Erweiterung folgt später.
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ export function planToRenderScene(plan: Plan, paperScaleN: number = STAMP_DEFAUL
|
|||||||
widthMm: p.weightMm,
|
widthMm: p.weightMm,
|
||||||
dash: p.dash ?? null,
|
dash: p.dash ?? null,
|
||||||
});
|
});
|
||||||
} else {
|
} else if (p.kind === "text") {
|
||||||
// "text": zeilenweise flachen — die ECHTEN Glyphen rastert der Rust-
|
// "text": zeilenweise flachen — die ECHTEN Glyphen rastert der Rust-
|
||||||
// Renderer über einen GPU-Glyphen-Atlas (glyphon, dieselbe Font-Familie
|
// Renderer über einen GPU-Glyphen-Atlas (glyphon, dieselbe Font-Familie
|
||||||
// wie im Browser). Layout identisch zum SVG-Pfad in PlanView (case
|
// wie im Browser). Layout identisch zum SVG-Pfad in PlanView (case
|
||||||
|
|||||||
+16
-2
@@ -21,6 +21,7 @@ import type { DrawingLevelKind, ImportedMesh, Project } from "../model/types";
|
|||||||
import type { DxfImportResult, ImportDiagnostics } from "../io/dxfParser";
|
import type { DxfImportResult, ImportDiagnostics } from "../io/dxfParser";
|
||||||
import {
|
import {
|
||||||
contoursToDrawings,
|
contoursToDrawings,
|
||||||
|
textsToDrawings,
|
||||||
countContours,
|
countContours,
|
||||||
distinctLayers,
|
distinctLayers,
|
||||||
} from "../io/dxfToDrawings";
|
} from "../io/dxfToDrawings";
|
||||||
@@ -107,9 +108,11 @@ export function ImportDialog({
|
|||||||
|
|
||||||
const contourSets = parsed?.contours ?? [];
|
const contourSets = parsed?.contours ?? [];
|
||||||
const meshes: ImportedMesh[] = parsed?.meshes ?? [];
|
const meshes: ImportedMesh[] = parsed?.meshes ?? [];
|
||||||
|
const texts = parsed?.texts ?? [];
|
||||||
const contourCount = countContours(contourSets);
|
const contourCount = countContours(contourSets);
|
||||||
|
const textCount = texts.length;
|
||||||
const layers = distinctLayers(contourSets);
|
const layers = distinctLayers(contourSets);
|
||||||
const hasDrawings = contourCount > 0;
|
const hasDrawings = contourCount > 0 || textCount > 0;
|
||||||
const hasMeshes = meshes.length > 0;
|
const hasMeshes = meshes.length > 0;
|
||||||
|
|
||||||
// ── Formular-Zustand ──────────────────────────────────────────────────────
|
// ── Formular-Zustand ──────────────────────────────────────────────────────
|
||||||
@@ -187,6 +190,9 @@ export function ImportDialog({
|
|||||||
</div>
|
</div>
|
||||||
<ul className="imp-summary">
|
<ul className="imp-summary">
|
||||||
<li>{t("import.summary.contours", { n: contourCount })}</li>
|
<li>{t("import.summary.contours", { n: contourCount })}</li>
|
||||||
|
{textCount > 0 && (
|
||||||
|
<li>{t("import.summary.texts", { n: textCount })}</li>
|
||||||
|
)}
|
||||||
<li>{t("import.summary.meshes", { n: meshes.length })}</li>
|
<li>{t("import.summary.meshes", { n: meshes.length })}</li>
|
||||||
<li>
|
<li>
|
||||||
{layers.length > 0
|
{layers.length > 0
|
||||||
@@ -348,11 +354,19 @@ export function buildDrawings(
|
|||||||
for (const c of project.layers) byName.set(c.name.toLowerCase(), c.code);
|
for (const c of project.layers) byName.set(c.name.toLowerCase(), c.code);
|
||||||
const layerToCode = (layerName: string): string =>
|
const layerToCode = (layerName: string): string =>
|
||||||
byName.get(layerName.toLowerCase()) ?? activeCategoryCode;
|
byName.get(layerName.toLowerCase()) ?? activeCategoryCode;
|
||||||
return contoursToDrawings(
|
const fromContours = contoursToDrawings(
|
||||||
decision.parsed.contours,
|
decision.parsed.contours,
|
||||||
levelId,
|
levelId,
|
||||||
decision.categoryMode,
|
decision.categoryMode,
|
||||||
activeCategoryCode,
|
activeCategoryCode,
|
||||||
layerToCode,
|
layerToCode,
|
||||||
);
|
);
|
||||||
|
const fromTexts = textsToDrawings(
|
||||||
|
decision.parsed.texts ?? [],
|
||||||
|
levelId,
|
||||||
|
decision.categoryMode,
|
||||||
|
activeCategoryCode,
|
||||||
|
layerToCode,
|
||||||
|
);
|
||||||
|
return [...fromContours, ...fromTexts];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user