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) ─────────────────────────
|
||||
onSetWallReferenceLine: (ref) => {
|
||||
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) => {
|
||||
if (selection?.kind === "wall")
|
||||
|
||||
@@ -302,6 +302,7 @@ export const de = {
|
||||
"objinfo.wall.refLine.left": "Außen (links)",
|
||||
"objinfo.wall.refLine.center": "Mitte",
|
||||
"objinfo.wall.refLine.right": "Innen (rechts)",
|
||||
"objinfo.wall.refLine.joint": "Fuge",
|
||||
"objinfo.wall.sliceTermination": "An Decke enden",
|
||||
"objinfo.wall.sliceTermination.both": "Aus",
|
||||
"objinfo.wall.sliceTermination.below": "Unten",
|
||||
|
||||
@@ -301,6 +301,7 @@ export const en: Record<TranslationKey, string> = {
|
||||
"objinfo.wall.refLine.left": "Exterior (left)",
|
||||
"objinfo.wall.refLine.center": "Center",
|
||||
"objinfo.wall.refLine.right": "Interior (right)",
|
||||
"objinfo.wall.refLine.joint": "Joint",
|
||||
"objinfo.wall.sliceTermination": "End at ceiling",
|
||||
"objinfo.wall.sliceTermination.both": "Off",
|
||||
"objinfo.wall.sliceTermination.below": "Below",
|
||||
|
||||
@@ -827,6 +827,14 @@ export interface Wall {
|
||||
* Verhalten: Schichten symmetrisch −T/2 … +T/2 um die Achse).
|
||||
*/
|
||||
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
|
||||
* baseElevation des zugehörigen Geschosses (= heutiges Verhalten).
|
||||
|
||||
+23
-2
@@ -2,8 +2,8 @@
|
||||
// ceilingVerticalExtent): reine Datenschicht, kein React/Store.
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { ceilingVerticalExtent } from "./wall";
|
||||
import type { Ceiling, Project } from "./types";
|
||||
import { ceilingVerticalExtent, wallReferenceOffset } from "./wall";
|
||||
import type { Ceiling, Project, Wall } from "./types";
|
||||
|
||||
/** Minimalprojekt mit einem Geschoss (baseElevation 0, floorHeight 2.6) und
|
||||
* 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)
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
totalThickness: number,
|
||||
): number {
|
||||
const ref = wall.referenceLine ?? "center";
|
||||
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 === "right") return -half;
|
||||
return 0;
|
||||
|
||||
@@ -906,17 +906,29 @@ export function WallSection({
|
||||
<div className="objinfo-sep" />
|
||||
<div className="objinfo-section-label">{t("objinfo.wall.section")}</div>
|
||||
|
||||
{/* Referenzlinie (wo die Achse über die Dicke liegt) — früher im ObjectInfo-
|
||||
Kopf, jetzt hier bei den Wand-Attributen. */}
|
||||
{/* Referenzlinie (wo die Achse über die Dicke liegt) — außen/mitte/innen ODER
|
||||
bei mehrschichtigen Wänden eine SCHICHTTRENNLINIE (Fuge). Ein Fugenwert
|
||||
wird als "off:<offset>" kodiert und übersteuert die benannte Linie. */}
|
||||
<div className="objinfo-field">
|
||||
<span className="objinfo-flabel">{t("objinfo.wall.refLine")}</span>
|
||||
<Dropdown
|
||||
value={wall.referenceLine}
|
||||
onChange={(v) => host.onSetWallReferenceLine(v as WallReferenceLine)}
|
||||
value={
|
||||
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={[
|
||||
{ value: "left", label: t("objinfo.wall.refLine.left") },
|
||||
{ value: "center", label: t("objinfo.wall.refLine.center") },
|
||||
{ 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")}
|
||||
width={120}
|
||||
|
||||
@@ -234,6 +234,11 @@ export interface PanelHostValue {
|
||||
// ── Wand-Attribute (Object-Info-Panel; wirken NUR auf die selektierte Wand) ─
|
||||
/** Setzt die Lage der Wandachse über die Dicke (außen/mitte/innen). */
|
||||
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):
|
||||
* "both" = heutiges Verhalten, "below"/"above" = die Wand endet an der Decke.
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ceilingThickness,
|
||||
flattenCategories,
|
||||
getCeilingType,
|
||||
getComponent,
|
||||
getWallType,
|
||||
wallTypeLabel,
|
||||
wallTypeThickness,
|
||||
@@ -69,6 +70,16 @@ export interface FloorChoice {
|
||||
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
|
||||
* einzelnen Wand gesetzt). Effektiv aufgelöste, anzeigefertige Werte plus die
|
||||
@@ -76,6 +87,14 @@ export interface FloorChoice {
|
||||
*/
|
||||
export interface WallInfo {
|
||||
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). */
|
||||
sliceTermination: SliceTermination;
|
||||
wallTypeId: string;
|
||||
@@ -435,8 +454,26 @@ function wallSelection(project: Project, wall: Wall): Selection {
|
||||
.filter((o) => o.hostWallId === wall.id)
|
||||
.reduce((sum, o) => sum + o.width * o.height * thickness, 0);
|
||||
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 = {
|
||||
referenceLine: wall.referenceLine ?? "center",
|
||||
referenceOffset: wall.referenceOffset,
|
||||
layerBoundaries,
|
||||
sliceTermination: wall.sliceTermination ?? "both",
|
||||
wallTypeId: wall.wallTypeId,
|
||||
thickness,
|
||||
|
||||
Reference in New Issue
Block a user