DXF-Import: Polyface-Mesh-Faces (POLYLINE) wurden nie erkannt — swissBUILDINGS3D-Gebäude blieben leer
Root Cause für „Gebäude-Import funktioniert nicht": Die installierte dxf-parser-Version liefert Face-Vertex-Indizes NICHT als `faces`-Array, sondern als vier einzelne Felder (Gruppencodes 71–74: faceA/faceB/faceC/ faceD). Unser addPolyfaceMesh/isMeshPolyline prüfte auf ein `faces`-Array, das in dieser Version nie existiert — jede POLYLINE-Polyface-Mesh (exakt das Format der swissBUILDINGS3D-DXF-Kacheln) ergab dadurch 0 Dreiecke, obwohl Positionen geschrieben wurden. Live gegen echte swissBUILDINGS3D-Kacheln verifiziert: vorher 0 Meshes, jetzt tausende Dreiecke mit realistischen Schweizer Höhenwerten. +3 Tests (rohes DXF, nicht gemockt, deckt auch die zugrundeliegende Bibliothek ab), Suite 794 grün.
This commit is contained in:
@@ -537,3 +537,65 @@ describe("contoursToDrawings — HATCH-Füllung", () => {
|
|||||||
expect(d.fillColor).toBeUndefined();
|
expect(d.fillColor).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("parseDxf — Polyface-Mesh (POLYLINE Flag 64, swissBUILDINGS3D-Format)", () => {
|
||||||
|
// Regression: die installierte dxf-parser-Version liefert Face-Vertex-
|
||||||
|
// Indizes NICHT als `faces`-Array, sondern als vier einzelne Felder
|
||||||
|
// (Gruppencodes 71–74, faceA..faceD) — reale swissBUILDINGS3D-DXF-Kacheln
|
||||||
|
// ergaben dadurch 0 Dreiecke (Nutzer-Report „Gebäude-Import funktioniert
|
||||||
|
// nicht"). Rohes DXF (keine gemockte Entity-Form), damit auch die
|
||||||
|
// zugrundeliegende Bibliothek mitgeprüft wird.
|
||||||
|
it("ein Dreieck (3 Geometrie-Vertices + 1 Face-Record) ergibt genau 1 Dreieck", () => {
|
||||||
|
const lines = [
|
||||||
|
"0", "SECTION", "2", "ENTITIES",
|
||||||
|
"0", "POLYLINE", "8", "0", "70", "64",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "0.0", "20", "0.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "1.0", "20", "0.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "0.0", "20", "1.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "128", "10", "0.0", "20", "0.0", "30", "0.0",
|
||||||
|
"71", "1", "72", "2", "73", "3",
|
||||||
|
"0", "SEQEND",
|
||||||
|
"0", "ENDSEC", "0", "EOF",
|
||||||
|
];
|
||||||
|
const res = parseDxf(lines.join("\n"));
|
||||||
|
expect(res.meshes).toHaveLength(1);
|
||||||
|
expect(res.meshes[0].positions.length / 3).toBe(3);
|
||||||
|
expect(res.meshes[0].indices).toEqual([0, 1, 2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ein Viereck (4 Geometrie-Vertices + 1 Face-Record mit faceD) ergibt 2 Dreiecke (Fan)", () => {
|
||||||
|
const lines = [
|
||||||
|
"0", "SECTION", "2", "ENTITIES",
|
||||||
|
"0", "POLYLINE", "8", "0", "70", "64",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "0.0", "20", "0.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "1.0", "20", "0.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "1.0", "20", "1.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "0.0", "20", "1.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "128", "10", "0.0", "20", "0.0", "30", "0.0",
|
||||||
|
"71", "1", "72", "2", "73", "3", "74", "4",
|
||||||
|
"0", "SEQEND",
|
||||||
|
"0", "ENDSEC", "0", "EOF",
|
||||||
|
];
|
||||||
|
const res = parseDxf(lines.join("\n"));
|
||||||
|
expect(res.meshes).toHaveLength(1);
|
||||||
|
expect(res.meshes[0].positions.length / 3).toBe(4);
|
||||||
|
expect(res.meshes[0].indices.length / 3).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("negative (unsichtbare-Kante) Face-Indizes werden per abs() aufgelöst", () => {
|
||||||
|
const lines = [
|
||||||
|
"0", "SECTION", "2", "ENTITIES",
|
||||||
|
"0", "POLYLINE", "8", "0", "70", "64",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "0.0", "20", "0.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "1.0", "20", "0.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "192", "10", "0.0", "20", "1.0", "30", "0.0",
|
||||||
|
"0", "VERTEX", "8", "0", "70", "128", "10", "0.0", "20", "0.0", "30", "0.0",
|
||||||
|
"71", "1", "72", "-2", "73", "3",
|
||||||
|
"0", "SEQEND",
|
||||||
|
"0", "ENDSEC", "0", "EOF",
|
||||||
|
];
|
||||||
|
const res = parseDxf(lines.join("\n"));
|
||||||
|
expect(res.meshes).toHaveLength(1);
|
||||||
|
expect(res.meshes[0].indices).toEqual([0, 1, 2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+26
-10
@@ -58,8 +58,17 @@ interface DxfVertex {
|
|||||||
x?: number;
|
x?: number;
|
||||||
y?: number;
|
y?: number;
|
||||||
z?: number;
|
z?: number;
|
||||||
// Polyface-Mesh: 1-basierte, ggf. negative Vertex-Indizes (negativ = unsichtbare Kante).
|
// Polyface-Mesh-Face-Record (VERTEX mit polyfaceMeshVertex-Flag): 1-basierte,
|
||||||
faces?: number[];
|
// ggf. negative Vertex-Indizes (negativ = unsichtbare Kante) — die installierte
|
||||||
|
// dxf-parser-Version liefert sie NICHT als `faces`-Array, sondern als vier
|
||||||
|
// EINZELNE Felder (Gruppencodes 71–74, ein Dreieck nutzt nur faceA–faceC,
|
||||||
|
// ein Viereck alle vier). `polyfaceMeshVertex` markiert einen Face- statt
|
||||||
|
// Geometrie-Vertex (Gruppencode-70-Bit 128, s. isMeshPolyline/addPolyfaceMesh).
|
||||||
|
faceA?: number;
|
||||||
|
faceB?: number;
|
||||||
|
faceC?: number;
|
||||||
|
faceD?: number;
|
||||||
|
polyfaceMeshVertex?: boolean;
|
||||||
}
|
}
|
||||||
interface DxfEntity {
|
interface DxfEntity {
|
||||||
type?: string;
|
type?: string;
|
||||||
@@ -295,18 +304,25 @@ function addMesh(positions: number[], indices: number[], e: DxfEntity): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Rohe Face-Vertex-Indizes eines VERTEX-Records (faceA..faceD, nur gesetzte,
|
||||||
|
* von Null verschiedene Felder — ein Dreieck belegt nur faceA–faceC). */
|
||||||
|
function faceIndicesOf(v: DxfVertex): number[] {
|
||||||
|
return [v.faceA, v.faceB, v.faceC, v.faceD].filter(
|
||||||
|
(i): i is number => typeof i === "number" && i !== 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Ob eine POLYLINE eine Mesh-Variante ist (Polyface- oder Polygon-Gitter). */
|
/** Ob eine POLYLINE eine Mesh-Variante ist (Polyface- oder Polygon-Gitter). */
|
||||||
function isMeshPolyline(e: DxfEntity): boolean {
|
function isMeshPolyline(e: DxfEntity): boolean {
|
||||||
if (e.isPolyfaceMesh === true || e.is3dPolygonMesh === true) return true;
|
if (e.isPolyfaceMesh === true || e.is3dPolygonMesh === true) return true;
|
||||||
// Heuristik: enthält irgendein Vertex eine `faces`-Liste → Polyface-Mesh.
|
// Heuristik: enthält irgendein Vertex Face-Indizes → Polyface-Mesh.
|
||||||
return (e.vertices ?? []).some(
|
return (e.vertices ?? []).some((v) => faceIndicesOf(v).length > 0);
|
||||||
(v) => Array.isArray(v.faces) && v.faces.length > 0,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Polyface-Mesh (POLYLINE, Flag 64): erst Geometrie-Vertices, dann Face-Records
|
* Polyface-Mesh (POLYLINE, Flag 64): erst Geometrie-Vertices, dann Face-Records
|
||||||
* mit 1-basierten (ggf. negativen = unsichtbare Kante) Vertex-Indizes. Wir
|
* (VERTEX mit `polyfaceMeshVertex`-Flag, s. {@link DxfVertex}) mit 1-basierten
|
||||||
|
* (ggf. negativen = unsichtbare Kante) Vertex-Indizes in faceA..faceD. Wir
|
||||||
* trennen Geometrie- von Face-Vertices und fan-triangulieren jede Face.
|
* trennen Geometrie- von Face-Vertices und fan-triangulieren jede Face.
|
||||||
*/
|
*/
|
||||||
function addPolyfaceMesh(
|
function addPolyfaceMesh(
|
||||||
@@ -315,8 +331,8 @@ function addPolyfaceMesh(
|
|||||||
e: DxfEntity,
|
e: DxfEntity,
|
||||||
): void {
|
): void {
|
||||||
const all = e.vertices ?? [];
|
const all = e.vertices ?? [];
|
||||||
const geom = all.filter((v) => !(Array.isArray(v.faces) && v.faces.length));
|
const geom = all.filter((v) => faceIndicesOf(v).length === 0);
|
||||||
const faceRecs = all.filter((v) => Array.isArray(v.faces) && v.faces.length);
|
const faceRecs = all.filter((v) => faceIndicesOf(v).length > 0);
|
||||||
if (geom.length < 3) return;
|
if (geom.length < 3) return;
|
||||||
const base = positions.length / 3;
|
const base = positions.length / 3;
|
||||||
for (const v of geom) {
|
for (const v of geom) {
|
||||||
@@ -324,7 +340,7 @@ function addPolyfaceMesh(
|
|||||||
}
|
}
|
||||||
for (const fr of faceRecs) {
|
for (const fr of faceRecs) {
|
||||||
// 1-basierte Indizes; Vorzeichen markiert (un)sichtbare Kanten → abs().
|
// 1-basierte Indizes; Vorzeichen markiert (un)sichtbare Kanten → abs().
|
||||||
const ring = (fr.faces ?? [])
|
const ring = faceIndicesOf(fr)
|
||||||
.map((i) => Math.abs(i))
|
.map((i) => Math.abs(i))
|
||||||
.filter((i) => i >= 1)
|
.filter((i) => i >= 1)
|
||||||
.map((i) => base + (i - 1));
|
.map((i) => base + (i - 1));
|
||||||
|
|||||||
Reference in New Issue
Block a user