Schichttrennlinie als Wand-Referenzlinie (mehrschichtige Wände)
Bei mehrschichtigen Wänden lässt sich neben Aussen/Mitte/Innen jetzt auch eine SCHICHTTRENNLINIE (Fuge zwischen zwei Schichten) als Referenzlinie wählen — z. B. die Achse auf die Innenkante der Aussenschale legen. - Neues Feld Wall.referenceOffset (freier Achsversatz entlang +n); wenn gesetzt, übersteuert es referenceLine. wallReferenceOffset bleibt die EINE Quelle → Grundriss, Schnitt und 3D erben das Verhalten automatisch. - Object-Info-Panel: das Referenzlinien-Dropdown listet zusätzlich je interne Fuge einen Eintrag (Kürzel der angrenzenden Schichten); Auswahl setzt den Versatz, Aussen/Mitte/Innen löscht ihn wieder. - selectionInfo berechnet die Fugen-Versätze (kumulierte Schichtdicken). +3 Tests. tsc + vitest grün.
This commit is contained in:
+6
-1
@@ -2968,7 +2968,12 @@ export default function App() {
|
|||||||
// ── Wand-Attribute (nur bei selektierter Wand) ─────────────────────────
|
// ── Wand-Attribute (nur bei selektierter Wand) ─────────────────────────
|
||||||
onSetWallReferenceLine: (ref) => {
|
onSetWallReferenceLine: (ref) => {
|
||||||
if (selection?.kind === "wall")
|
if (selection?.kind === "wall")
|
||||||
updateWall(selection.id, { referenceLine: ref });
|
// Benannte Linie gewählt ⇒ freien Fugen-Versatz löschen (er übersteuert sonst).
|
||||||
|
updateWall(selection.id, { referenceLine: ref, referenceOffset: undefined });
|
||||||
|
},
|
||||||
|
onSetWallReferenceOffset: (offset) => {
|
||||||
|
if (selection?.kind === "wall")
|
||||||
|
updateWall(selection.id, { referenceOffset: offset ?? undefined });
|
||||||
},
|
},
|
||||||
onSetWallSliceTermination: (termination) => {
|
onSetWallSliceTermination: (termination) => {
|
||||||
if (selection?.kind === "wall")
|
if (selection?.kind === "wall")
|
||||||
|
|||||||
@@ -302,6 +302,7 @@ export const de = {
|
|||||||
"objinfo.wall.refLine.left": "Außen (links)",
|
"objinfo.wall.refLine.left": "Außen (links)",
|
||||||
"objinfo.wall.refLine.center": "Mitte",
|
"objinfo.wall.refLine.center": "Mitte",
|
||||||
"objinfo.wall.refLine.right": "Innen (rechts)",
|
"objinfo.wall.refLine.right": "Innen (rechts)",
|
||||||
|
"objinfo.wall.refLine.joint": "Fuge",
|
||||||
"objinfo.wall.sliceTermination": "An Decke enden",
|
"objinfo.wall.sliceTermination": "An Decke enden",
|
||||||
"objinfo.wall.sliceTermination.both": "Aus",
|
"objinfo.wall.sliceTermination.both": "Aus",
|
||||||
"objinfo.wall.sliceTermination.below": "Unten",
|
"objinfo.wall.sliceTermination.below": "Unten",
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"objinfo.wall.refLine.left": "Exterior (left)",
|
"objinfo.wall.refLine.left": "Exterior (left)",
|
||||||
"objinfo.wall.refLine.center": "Center",
|
"objinfo.wall.refLine.center": "Center",
|
||||||
"objinfo.wall.refLine.right": "Interior (right)",
|
"objinfo.wall.refLine.right": "Interior (right)",
|
||||||
|
"objinfo.wall.refLine.joint": "Joint",
|
||||||
"objinfo.wall.sliceTermination": "End at ceiling",
|
"objinfo.wall.sliceTermination": "End at ceiling",
|
||||||
"objinfo.wall.sliceTermination.both": "Off",
|
"objinfo.wall.sliceTermination.both": "Off",
|
||||||
"objinfo.wall.sliceTermination.below": "Below",
|
"objinfo.wall.sliceTermination.below": "Below",
|
||||||
|
|||||||
@@ -827,6 +827,14 @@ export interface Wall {
|
|||||||
* Verhalten: Schichten symmetrisch −T/2 … +T/2 um die Achse).
|
* Verhalten: Schichten symmetrisch −T/2 … +T/2 um die Achse).
|
||||||
*/
|
*/
|
||||||
referenceLine?: WallReferenceLine;
|
referenceLine?: WallReferenceLine;
|
||||||
|
/**
|
||||||
|
* Freier Referenz-Versatz der Achse entlang der +n-Normalen von der Wandmitte
|
||||||
|
* (Meter). Ist er gesetzt, ÜBERSTEUERT er {@link referenceLine} — genutzt, um
|
||||||
|
* bei mehrschichtigen Wänden eine SCHICHTTRENNLINIE (Fuge zwischen zwei
|
||||||
|
* Schichten) als Referenzlinie zu wählen. +T/2 = Aussenfläche, 0 = Mitte,
|
||||||
|
* −T/2 = Innenfläche; ein interner Fugenwert liegt dazwischen.
|
||||||
|
*/
|
||||||
|
referenceOffset?: number;
|
||||||
/**
|
/**
|
||||||
* Vertikale Bindung der Unterkante (UK). Fehlt sie, sitzt die UK auf der
|
* Vertikale Bindung der Unterkante (UK). Fehlt sie, sitzt die UK auf der
|
||||||
* baseElevation des zugehörigen Geschosses (= heutiges Verhalten).
|
* baseElevation des zugehörigen Geschosses (= heutiges Verhalten).
|
||||||
|
|||||||
+23
-2
@@ -2,8 +2,8 @@
|
|||||||
// ceilingVerticalExtent): reine Datenschicht, kein React/Store.
|
// ceilingVerticalExtent): reine Datenschicht, kein React/Store.
|
||||||
|
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { ceilingVerticalExtent } from "./wall";
|
import { ceilingVerticalExtent, wallReferenceOffset } from "./wall";
|
||||||
import type { Ceiling, Project } from "./types";
|
import type { Ceiling, Project, Wall } from "./types";
|
||||||
|
|
||||||
/** Minimalprojekt mit einem Geschoss (baseElevation 0, floorHeight 2.6) und
|
/** Minimalprojekt mit einem Geschoss (baseElevation 0, floorHeight 2.6) und
|
||||||
* einem einschichtigen Wandtyp (0.2 m), den die Decke als Aufbau nutzt. */
|
* einem einschichtigen Wandtyp (0.2 m), den die Decke als Aufbau nutzt. */
|
||||||
@@ -113,3 +113,24 @@ describe("ceilingVerticalExtent", () => {
|
|||||||
expect(zBottom).toBeCloseTo(2.9); // baseElevation(og)=2.8 + offset(0.1)
|
expect(zBottom).toBeCloseTo(2.9); // baseElevation(og)=2.8 + offset(0.1)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("wallReferenceOffset", () => {
|
||||||
|
const w = (extra: Partial<Wall>): Wall => ({ referenceLine: "center", ...extra } as Wall);
|
||||||
|
const T = 0.4; // Gesamtdicke → half = 0.2
|
||||||
|
|
||||||
|
it("bildet die benannten Linien ab (aussen/mitte/innen)", () => {
|
||||||
|
expect(wallReferenceOffset(w({ referenceLine: "left" }), T)).toBeCloseTo(0.2, 6);
|
||||||
|
expect(wallReferenceOffset(w({ referenceLine: "center" }), T)).toBeCloseTo(0, 6);
|
||||||
|
expect(wallReferenceOffset(w({ referenceLine: "right" }), T)).toBeCloseTo(-0.2, 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("übersteuert die Referenzlinie mit einem freien Fugen-Versatz", () => {
|
||||||
|
// Fuge z. B. nach einer 0.05-Aussenschale: offset = half − 0.05 = 0.15.
|
||||||
|
expect(wallReferenceOffset(w({ referenceLine: "left", referenceOffset: 0.15 }), T)).toBeCloseTo(0.15, 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("klemmt den Fugen-Versatz auf ±halbe Dicke", () => {
|
||||||
|
expect(wallReferenceOffset(w({ referenceOffset: 5 }), T)).toBeCloseTo(0.2, 6);
|
||||||
|
expect(wallReferenceOffset(w({ referenceOffset: -5 }), T)).toBeCloseTo(-0.2, 6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+7
-1
@@ -26,8 +26,14 @@ export function wallReferenceOffset(
|
|||||||
wall: Wall,
|
wall: Wall,
|
||||||
totalThickness: number,
|
totalThickness: number,
|
||||||
): number {
|
): number {
|
||||||
const ref = wall.referenceLine ?? "center";
|
|
||||||
const half = totalThickness / 2;
|
const half = totalThickness / 2;
|
||||||
|
// Freier Fugen-Versatz (Schichttrennlinie als Referenz) übersteuert die
|
||||||
|
// benannte Referenzlinie. Auf die Wanddicke geklemmt, damit die Achse nie
|
||||||
|
// ausserhalb der Wand landet.
|
||||||
|
if (wall.referenceOffset != null) {
|
||||||
|
return Math.max(-half, Math.min(half, wall.referenceOffset));
|
||||||
|
}
|
||||||
|
const ref = wall.referenceLine ?? "center";
|
||||||
if (ref === "left") return +half;
|
if (ref === "left") return +half;
|
||||||
if (ref === "right") return -half;
|
if (ref === "right") return -half;
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -906,17 +906,29 @@ export function WallSection({
|
|||||||
<div className="objinfo-sep" />
|
<div className="objinfo-sep" />
|
||||||
<div className="objinfo-section-label">{t("objinfo.wall.section")}</div>
|
<div className="objinfo-section-label">{t("objinfo.wall.section")}</div>
|
||||||
|
|
||||||
{/* Referenzlinie (wo die Achse über die Dicke liegt) — früher im ObjectInfo-
|
{/* Referenzlinie (wo die Achse über die Dicke liegt) — außen/mitte/innen ODER
|
||||||
Kopf, jetzt hier bei den Wand-Attributen. */}
|
bei mehrschichtigen Wänden eine SCHICHTTRENNLINIE (Fuge). Ein Fugenwert
|
||||||
|
wird als "off:<offset>" kodiert und übersteuert die benannte Linie. */}
|
||||||
<div className="objinfo-field">
|
<div className="objinfo-field">
|
||||||
<span className="objinfo-flabel">{t("objinfo.wall.refLine")}</span>
|
<span className="objinfo-flabel">{t("objinfo.wall.refLine")}</span>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
value={wall.referenceLine}
|
value={
|
||||||
onChange={(v) => host.onSetWallReferenceLine(v as WallReferenceLine)}
|
wall.referenceOffset != null
|
||||||
|
? `off:${wall.referenceOffset.toFixed(4)}`
|
||||||
|
: wall.referenceLine
|
||||||
|
}
|
||||||
|
onChange={(v) => {
|
||||||
|
if (v.startsWith("off:")) host.onSetWallReferenceOffset(Number(v.slice(4)));
|
||||||
|
else host.onSetWallReferenceLine(v as WallReferenceLine);
|
||||||
|
}}
|
||||||
options={[
|
options={[
|
||||||
{ value: "left", label: t("objinfo.wall.refLine.left") },
|
{ value: "left", label: t("objinfo.wall.refLine.left") },
|
||||||
{ value: "center", label: t("objinfo.wall.refLine.center") },
|
{ value: "center", label: t("objinfo.wall.refLine.center") },
|
||||||
{ value: "right", label: t("objinfo.wall.refLine.right") },
|
{ value: "right", label: t("objinfo.wall.refLine.right") },
|
||||||
|
...wall.layerBoundaries.map((b) => ({
|
||||||
|
value: `off:${b.offset.toFixed(4)}`,
|
||||||
|
label: `${t("objinfo.wall.refLine.joint")} ${b.label}`,
|
||||||
|
})),
|
||||||
]}
|
]}
|
||||||
title={t("objinfo.wall.refLine")}
|
title={t("objinfo.wall.refLine")}
|
||||||
width={120}
|
width={120}
|
||||||
|
|||||||
@@ -234,6 +234,11 @@ export interface PanelHostValue {
|
|||||||
// ── Wand-Attribute (Object-Info-Panel; wirken NUR auf die selektierte Wand) ─
|
// ── Wand-Attribute (Object-Info-Panel; wirken NUR auf die selektierte Wand) ─
|
||||||
/** Setzt die Lage der Wandachse über die Dicke (außen/mitte/innen). */
|
/** Setzt die Lage der Wandachse über die Dicke (außen/mitte/innen). */
|
||||||
onSetWallReferenceLine: (ref: WallReferenceLine) => void;
|
onSetWallReferenceLine: (ref: WallReferenceLine) => void;
|
||||||
|
/**
|
||||||
|
* Setzt einen freien Achsversatz (Schichttrennlinie als Referenz) oder löscht
|
||||||
|
* ihn (`null` → zurück zur benannten Referenzlinie). Meter entlang +n.
|
||||||
|
*/
|
||||||
|
onSetWallReferenceOffset: (offset: number | null) => void;
|
||||||
/**
|
/**
|
||||||
* Setzt die Terminierungs-Regel am Deckenanschluss (Zuschnitt, NICHT Priorität):
|
* Setzt die Terminierungs-Regel am Deckenanschluss (Zuschnitt, NICHT Priorität):
|
||||||
* "both" = heutiges Verhalten, "below"/"above" = die Wand endet an der Decke.
|
* "both" = heutiges Verhalten, "below"/"above" = die Wand endet an der Decke.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
ceilingThickness,
|
ceilingThickness,
|
||||||
flattenCategories,
|
flattenCategories,
|
||||||
getCeilingType,
|
getCeilingType,
|
||||||
|
getComponent,
|
||||||
getWallType,
|
getWallType,
|
||||||
wallTypeLabel,
|
wallTypeLabel,
|
||||||
wallTypeThickness,
|
wallTypeThickness,
|
||||||
@@ -69,6 +70,16 @@ export interface FloorChoice {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine wählbare Schichttrennlinie (Fuge zwischen zwei Schichten) als
|
||||||
|
* Referenzlinie einer mehrschichtigen Wand. `offset` ist der fertige
|
||||||
|
* Achsversatz entlang +n (Meter), `label` benennt die angrenzenden Schichten.
|
||||||
|
*/
|
||||||
|
export interface LayerBoundaryChoice {
|
||||||
|
offset: number;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wand-spezifische Attribute für das Object-Info-Panel (nur bei Auswahl einer
|
* Wand-spezifische Attribute für das Object-Info-Panel (nur bei Auswahl einer
|
||||||
* einzelnen Wand gesetzt). Effektiv aufgelöste, anzeigefertige Werte plus die
|
* einzelnen Wand gesetzt). Effektiv aufgelöste, anzeigefertige Werte plus die
|
||||||
@@ -76,6 +87,14 @@ export interface FloorChoice {
|
|||||||
*/
|
*/
|
||||||
export interface WallInfo {
|
export interface WallInfo {
|
||||||
referenceLine: WallReferenceLine;
|
referenceLine: WallReferenceLine;
|
||||||
|
/** Freier Achsversatz (Schichttrennlinie), falls gesetzt — sonst undefined. */
|
||||||
|
referenceOffset?: number;
|
||||||
|
/**
|
||||||
|
* Interne Schichttrennlinien der Wand als wählbare Referenz (leer bei
|
||||||
|
* einschichtigen Wänden). Aussen-/Innenfläche stecken bereits in
|
||||||
|
* left/right — hier stehen nur die FUGEN dazwischen.
|
||||||
|
*/
|
||||||
|
layerBoundaries: LayerBoundaryChoice[];
|
||||||
/** Terminierungs-Regel am Deckenanschluss (Default "both" = heutiges Verhalten). */
|
/** Terminierungs-Regel am Deckenanschluss (Default "both" = heutiges Verhalten). */
|
||||||
sliceTermination: SliceTermination;
|
sliceTermination: SliceTermination;
|
||||||
wallTypeId: string;
|
wallTypeId: string;
|
||||||
@@ -435,8 +454,26 @@ function wallSelection(project: Project, wall: Wall): Selection {
|
|||||||
.filter((o) => o.hostWallId === wall.id)
|
.filter((o) => o.hostWallId === wall.id)
|
||||||
.reduce((sum, o) => sum + o.width * o.height * thickness, 0);
|
.reduce((sum, o) => sum + o.width * o.height * thickness, 0);
|
||||||
const netVolume = Math.max(0, grossVolume - openingVolume);
|
const netVolume = Math.max(0, grossVolume - openingVolume);
|
||||||
|
// Interne Schichttrennlinien als wählbare Referenz: von aussen (Schicht 0)
|
||||||
|
// nach innen aufsummiert; der Achsversatz +n = halbe Dicke − Fugenlage.
|
||||||
|
const half = thickness / 2;
|
||||||
|
const layerBoundaries: WallInfo["layerBoundaries"] = [];
|
||||||
|
let cum = 0;
|
||||||
|
const abbrevOf = (componentId: string): string => {
|
||||||
|
const c = getComponent(project, componentId);
|
||||||
|
return c ? c.abbrev ?? c.name : "?";
|
||||||
|
};
|
||||||
|
for (let i = 0; i < wt.layers.length - 1; i++) {
|
||||||
|
cum += wt.layers[i].thickness;
|
||||||
|
layerBoundaries.push({
|
||||||
|
offset: half - cum,
|
||||||
|
label: `${abbrevOf(wt.layers[i].componentId)}|${abbrevOf(wt.layers[i + 1].componentId)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
const wallInfo: WallInfo = {
|
const wallInfo: WallInfo = {
|
||||||
referenceLine: wall.referenceLine ?? "center",
|
referenceLine: wall.referenceLine ?? "center",
|
||||||
|
referenceOffset: wall.referenceOffset,
|
||||||
|
layerBoundaries,
|
||||||
sliceTermination: wall.sliceTermination ?? "both",
|
sliceTermination: wall.sliceTermination ?? "both",
|
||||||
wallTypeId: wall.wallTypeId,
|
wallTypeId: wall.wallTypeId,
|
||||||
thickness,
|
thickness,
|
||||||
|
|||||||
Reference in New Issue
Block a user