Dächer anwählbar + editierbar + löschbar
Platzierte Dächer sind jetzt vollwertige Auswahl-Elemente (analog Decke): - 2D-Auswahl per Klick: unsichtbares Traufe-Pick-Polygon (roofId) + pickRoof (Punkt-in-Polygon, niedrigste Priorität) in PlanView. - Auswahl-Kanal selectedRoofIds (selectionSlice), updateRoof (projectSlice), RoofInfo + roofSelection + deriveSelection-Dispatch (selectionInfo). - Attribut-Panel „Dach": Form (Sattel/Walm/Pult/Mansarde/Zelt/Flach), Firstrichtung X/Y, Neigung(en), Überstand, Dachdicke — live editierbar über host.onSetRoofPatch; dazu abgeleitete Firsthöhe + Grundfläche (read-only). - Löschen (Delete) + Abwählen bei Geschosswechsel/Marquee/anderer Auswahl. +2 Tests (Roof-Selection). tsc + vitest 642 + vite build grün.
This commit is contained in:
@@ -30,6 +30,8 @@ import type {
|
||||
LayerCategory,
|
||||
Opening,
|
||||
Project,
|
||||
Roof,
|
||||
RoofShape,
|
||||
Room,
|
||||
SiaCategory,
|
||||
SliceTermination,
|
||||
@@ -49,6 +51,7 @@ import {
|
||||
wallVerticalExtent,
|
||||
} from "../model/wall";
|
||||
import { ceilingArea, outlineBBox } from "../geometry/ceiling";
|
||||
import { roofBBox, roofBaseElevation, roofGeometry } from "../geometry/roof";
|
||||
import { openingGapQuad, openingVerticalExtent } from "../geometry/opening";
|
||||
import { stairGeometry, stairBBox } from "../geometry/stair";
|
||||
|
||||
@@ -70,6 +73,25 @@ export interface FloorChoice {
|
||||
name: string;
|
||||
}
|
||||
|
||||
/** Dach-Attribute fürs Objekt-Info-Panel (nur bei Auswahl genau eines Dachs). */
|
||||
export interface RoofInfo {
|
||||
shape: RoofShape;
|
||||
/** Hauptneigung (Grad). */
|
||||
pitchDeg: number;
|
||||
/** Obere Neigung (Grad) — nur Mansarde relevant. */
|
||||
pitchUpperDeg?: number;
|
||||
/** Dachüberstand an der Traufe (Meter). */
|
||||
overhang: number;
|
||||
/** Firstrichtung entlang X oder Y. */
|
||||
ridgeAxis: "x" | "y";
|
||||
/** Dachdicke (Meter). */
|
||||
thickness: number;
|
||||
/** Firsthöhe über der Traufe (Meter, abgeleitet). */
|
||||
ridgeHeight: number;
|
||||
/** Grundfläche des Umriss-Rechtecks (m², ohne Überstand). */
|
||||
footprintArea: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eine wählbare Schichttrennlinie (Fuge zwischen zwei Schichten) als
|
||||
* Referenzlinie einer mehrschichtigen Wand. `offset` ist der fertige
|
||||
@@ -299,7 +321,8 @@ export interface Selection {
|
||||
| "drawing2d"
|
||||
| "door"
|
||||
| "extrudedSolid"
|
||||
| "column";
|
||||
| "column"
|
||||
| "roof";
|
||||
id: string;
|
||||
/** Grafik-Kategorie (Ebene) des Elements. */
|
||||
categoryCode: string;
|
||||
@@ -357,6 +380,8 @@ export interface Selection {
|
||||
wall?: WallInfo;
|
||||
/** Decken-Attribute (nur bei `kind === "ceiling"`). */
|
||||
ceiling?: CeilingInfo;
|
||||
/** Dach-Attribute (nur bei `kind === "roof"`). */
|
||||
roof?: RoofInfo;
|
||||
/** Öffnungs-Attribute (nur bei `kind === "opening"`). */
|
||||
opening?: OpeningInfo;
|
||||
/** Treppen-Attribute (nur bei `kind === "stair"`). */
|
||||
@@ -577,6 +602,37 @@ function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
|
||||
};
|
||||
}
|
||||
|
||||
/** Selektion für ein Dach ableiten (Farbe übersteuerbar, Strichstärke = Kategorie). */
|
||||
function roofSelection(project: Project, roof: Roof): Selection {
|
||||
const cat = findCategory(project.layers, roof.categoryCode);
|
||||
const color = roof.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
|
||||
const weightMm = cat?.lw ?? WALL_FALLBACK_MM;
|
||||
const bb = roofBBox(roof.outline);
|
||||
const eavesZ = roofBaseElevation(project, roof);
|
||||
const g = roofGeometry(roof, eavesZ);
|
||||
const footprintArea = Math.abs(bb.x1 - bb.x0) * Math.abs(bb.y1 - bb.y0);
|
||||
const roofInfo: RoofInfo = {
|
||||
shape: roof.shape,
|
||||
pitchDeg: roof.pitchDeg,
|
||||
pitchUpperDeg: roof.pitchUpperDeg,
|
||||
overhang: roof.overhang,
|
||||
ridgeAxis: roof.ridgeAxis,
|
||||
thickness: roof.thickness,
|
||||
ridgeHeight: g.ridgeHeight,
|
||||
footprintArea,
|
||||
};
|
||||
return {
|
||||
kind: "roof",
|
||||
id: roof.id,
|
||||
categoryCode: roof.categoryCode,
|
||||
color,
|
||||
weightMm,
|
||||
closed: true,
|
||||
bbox: { minX: bb.x0, minY: bb.y0, maxX: bb.x1, maxY: bb.y1 },
|
||||
roof: roofInfo,
|
||||
};
|
||||
}
|
||||
|
||||
/** Selektion für eine Öffnung ableiten (Farbe übersteuerbar, Strichstärke = Kategorie). */
|
||||
function openingSelection(project: Project, o: Opening): Selection {
|
||||
const cat = findCategory(project.layers, o.categoryCode);
|
||||
@@ -847,6 +903,7 @@ export function deriveSelection(
|
||||
selectedRoomId: string | null = null,
|
||||
selectedExtrudedSolidId: string | null = null,
|
||||
selectedColumnId: string | null = null,
|
||||
selectedRoofId: string | null = null,
|
||||
): Selection | null {
|
||||
if (selectedDrawingId) {
|
||||
const d = project.drawings2d.find((x) => x.id === selectedDrawingId);
|
||||
@@ -873,6 +930,10 @@ export function deriveSelection(
|
||||
const c = (project.ceilings ?? []).find((x) => x.id === selectedCeilingId);
|
||||
return c ? ceilingSelection(project, c) : null;
|
||||
}
|
||||
if (selectedRoofId) {
|
||||
const r = (project.roofs ?? []).find((x) => x.id === selectedRoofId);
|
||||
return r ? roofSelection(project, r) : null;
|
||||
}
|
||||
if (selectedStairId) {
|
||||
const s = (project.stairs ?? []).find((x) => x.id === selectedStairId);
|
||||
return s ? stairSelection(project, s) : null;
|
||||
|
||||
Reference in New Issue
Block a user