Gruppen-Isolation: Rahmen im Viewport, Knopf dort platziert, Bbox-Auswahl
Drei Verbesserungen nach Vectorworks-Vorbild (Screenshot-Referenz): - Sichtbarer Rahmen um die ganze Ansicht, solange eine Gruppe betreten ist (statt nur der gedimmten Elemente ohne Kontur). - "Gruppe verlassen" sitzt jetzt oben rechts IM Viewport (wie in Vectorworks), nicht mehr als lose schwebender Knopf über der ganzen App. - Ist eine GRUPPE (nicht einzelne Elemente) gewählt, zeigt die Auswahl nur noch eine gestrichelte Bounding-Box mit vier Eckpunkten statt jeden einzelnen Mitglieds-Umriss einzeln zu umranden.
This commit is contained in:
+2
-13
@@ -2857,6 +2857,7 @@ export default function App() {
|
||||
selectedStairIds={selectedStairIds}
|
||||
selectedExtrudedSolidIds={selectedExtrudedSolidIds}
|
||||
selectedColumnIds={selectedColumnIds}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
selectedRoomIds={selectedRoomIds}
|
||||
selectedRoofIds={selectedRoofIds}
|
||||
selectedContextObjectIds={selectedContextObjectIds}
|
||||
@@ -2866,6 +2867,7 @@ export default function App() {
|
||||
onStampEdit={onOpenStampEditor}
|
||||
onEditDrawingText={onEditDrawingText}
|
||||
onEnterGroup={onEnterGroup}
|
||||
onLeaveGroup={onLeaveGroup}
|
||||
groupPath={groupPath}
|
||||
editTextId={editTextId}
|
||||
editTextMinHeightM={editTextMinHeightM}
|
||||
@@ -2906,19 +2908,6 @@ export default function App() {
|
||||
onEdit3dWallTop={onEdit3dWallTop}
|
||||
onEdit3dEnd={onEdit3dEnd}
|
||||
/>
|
||||
{/* "Gruppe verlassen" (Vectorworks-artig): sichtbar, solange eine
|
||||
Gruppe betreten ist (Isolation — alles andere gedimmt/gesperrt,
|
||||
s. viewportContent.tsx). Verlässt genau EIN Level. */}
|
||||
{groupPath.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="plan-leave-group-btn"
|
||||
onClick={onLeaveGroup}
|
||||
title={t("group.leave.hint")}
|
||||
>
|
||||
{t("group.leave")}
|
||||
</button>
|
||||
)}
|
||||
{/* Command-Line (Rhino-artig) UNTER dem Viewport — nur Viewport-breit.
|
||||
Prompt + klickbare Inline-Optionen + Texteingabe + Autocomplete. */}
|
||||
<CommandLine
|
||||
|
||||
@@ -327,6 +327,13 @@ export interface PlanViewProps {
|
||||
* markiert den Querschnitt-Umriss JEDER dieser Stützen mit einer Akzentkontur.
|
||||
*/
|
||||
selectedColumnIds?: string[];
|
||||
/**
|
||||
* IDs der gewählten GRUPPEN (Vectorworks-artig). Nicht-leer → die Ansicht
|
||||
* zeigt statt der einzelnen Mitglieds-Umrisse nur eine Bounding-Box mit vier
|
||||
* Eckpunkten (s. `groupBBox`); die per-Kind-`selected…Ids`-Felder tragen
|
||||
* weiterhin die volle Mitgliedschaft (für Verschieben/Griffe/Löschen).
|
||||
*/
|
||||
selectedGroupIds?: string[];
|
||||
/**
|
||||
* Der Stempel-Griff des (einzeln) gewählten Raums: sein Anker (Meter) + ID.
|
||||
* PlanView zeichnet einen ziehbaren Griff und meldet Bewegung/Doppelklick.
|
||||
@@ -525,6 +532,7 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
|
||||
selectedSectionLineIds,
|
||||
selectedExtrudedSolidIds,
|
||||
selectedColumnIds,
|
||||
selectedGroupIds,
|
||||
roomStamp,
|
||||
onStampMove,
|
||||
onStampEdit,
|
||||
@@ -2270,6 +2278,48 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
|
||||
);
|
||||
}, [plan, selectedColumnIds]);
|
||||
|
||||
// Ist eine GRUPPE gewählt (statt einzelner Elemente), zeigt die Auswahl NUR
|
||||
// eine einfache Bounding-Box mit vier Eckpunkten statt jedes einzelnen
|
||||
// Mitglieds-Umrisses (Nutzer-Wunsch, Vectorworks-Vorbild: „einfach vier
|
||||
// Aussenpunkte zeigen") — vereint dieselben Hervorhebungs-Listen, die sonst
|
||||
// einzeln gezeichnet würden.
|
||||
const groupBBox = useMemo(() => {
|
||||
if (!selectedGroupIds || selectedGroupIds.length === 0) return null;
|
||||
const pts: Vec2[] = [];
|
||||
for (const poly of highlightPolys) pts.push(...poly.pts);
|
||||
for (const poly of highlightCeilingPolys) pts.push(...poly.pts);
|
||||
for (const poly of highlightRoomPolys) pts.push(...poly.pts);
|
||||
for (const poly of highlightRoofPolys) pts.push(...poly.pts);
|
||||
for (const poly of highlightColumnPolys) pts.push(...poly.pts);
|
||||
const fromLineOrPoly = (p: { kind: string; a?: Vec2; b?: Vec2; from?: Vec2; to?: Vec2; center?: Vec2; pts?: Vec2[] }) => {
|
||||
if (p.kind === "line" && p.a && p.b) pts.push(p.a, p.b);
|
||||
else if (p.kind === "arc" && p.from && p.to) pts.push(p.from, p.to);
|
||||
else if (p.pts) pts.push(...p.pts);
|
||||
};
|
||||
for (const p of highlightDrawPrims) fromLineOrPoly(p);
|
||||
for (const p of highlightOpeningPrims) fromLineOrPoly(p);
|
||||
for (const p of highlightStairPrims) fromLineOrPoly(p);
|
||||
if (pts.length === 0) return null;
|
||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||
for (const p of pts) {
|
||||
minX = Math.min(minX, p.x);
|
||||
minY = Math.min(minY, p.y);
|
||||
maxX = Math.max(maxX, p.x);
|
||||
maxY = Math.max(maxY, p.y);
|
||||
}
|
||||
return { minX, minY, maxX, maxY };
|
||||
}, [
|
||||
selectedGroupIds,
|
||||
highlightPolys,
|
||||
highlightCeilingPolys,
|
||||
highlightRoomPolys,
|
||||
highlightRoofPolys,
|
||||
highlightColumnPolys,
|
||||
highlightDrawPrims,
|
||||
highlightOpeningPrims,
|
||||
highlightStairPrims,
|
||||
]);
|
||||
|
||||
// Zusammenhängende 2D-Zeichen-Linien (gleiche drawingId + Stil) zu Zügen
|
||||
// bündeln, damit sie als EIN <polyline>/<polygon> gezeichnet werden können —
|
||||
// so gehren dicke Ecken sauber (kein Butt-Cap-Sprung). generatePlan schiebt die
|
||||
@@ -2461,6 +2511,11 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
|
||||
CAD-typische Gehrung an den Ecken (stroke-linejoin), keine Stufe bei
|
||||
dicken Linien. Nach der Haupt-Ebene gezeichnet (2D liegt ohnehin oben). */}
|
||||
{drawingRunEls}
|
||||
{/* Ist eine GRUPPE gewählt, ersetzt die Bounding-Box unten die einzelnen
|
||||
Mitglieds-Hervorhebungen (s. `groupBBox`) — dieser ganze Block bleibt
|
||||
dann aus. */}
|
||||
{!groupBBox && (
|
||||
<>
|
||||
{/* Auswahl-Hervorhebung: alle Bänder der gewählten Wand (bzw. das lokal
|
||||
getroffene Polygon) mit Akzentkontur überzeichnen. */}
|
||||
{highlightPolys.map((poly, i) => (
|
||||
@@ -2640,6 +2695,38 @@ export const PlanView = forwardRef<PlanViewHandle, PlanViewProps>(
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* Gruppen-Auswahl: nur eine Bounding-Box mit vier Eckpunkten (kein
|
||||
Umriss jedes einzelnen Mitglieds) — s. `groupBBox`. */}
|
||||
{groupBBox && (() => {
|
||||
const c1 = toScreen({ x: groupBBox.minX, y: groupBBox.minY });
|
||||
const c2 = toScreen({ x: groupBBox.maxX, y: groupBBox.minY });
|
||||
const c3 = toScreen({ x: groupBBox.maxX, y: groupBBox.maxY });
|
||||
const c4 = toScreen({ x: groupBBox.minX, y: groupBBox.maxY });
|
||||
const size = 5 / (meetScale(view, svgRef.current) ?? 1);
|
||||
const corners = [c1, c2, c3, c4];
|
||||
return (
|
||||
<>
|
||||
<polygon
|
||||
className="plan-group-bbox"
|
||||
points={corners.map((s) => `${s.x},${s.y}`).join(" ")}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
{corners.map((s, i) => (
|
||||
<rect
|
||||
key={`gbb-${i}`}
|
||||
className="plan-group-bbox-corner"
|
||||
x={s.x - size}
|
||||
y={s.y - size}
|
||||
width={size * 2}
|
||||
height={size * 2}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
{/* Auswahl-Rechteck (Marquee). Gestrichelt; die crossing-Variante (rechts→
|
||||
links, berührend) hebt sich farblich/strichlich ab. Das Rechteck steht
|
||||
bereits in viewBox-Einheiten → direkt setzbar. */}
|
||||
|
||||
+43
-17
@@ -2110,6 +2110,23 @@ body {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
/* Gruppen-Auswahl (Vectorworks-artig): statt jedes Mitglieds-Umrisses nur eine
|
||||
gestrichelte Bounding-Box mit vier Eckpunkt-Griffen — dieselbe Akzentfarbe
|
||||
wie die normale Auswahl, aber gestrichelt (sofort als "Gruppe" erkennbar). */
|
||||
.plan-svg .plan-group-bbox {
|
||||
fill: none;
|
||||
stroke: var(--accent);
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: 6 4;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
.plan-svg .plan-group-bbox-corner {
|
||||
fill: var(--accent);
|
||||
stroke: #fff;
|
||||
stroke-width: 1.5;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
/* Hervorhebung selektierter 2D-Zeichenelemente (Mehrfach/Marquee): Akzentkontur
|
||||
über der Geometrie, keine Füllung. */
|
||||
.plan-svg .plan-sel-draw {
|
||||
@@ -2119,25 +2136,34 @@ body {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
/* "Gruppe verlassen" (Vectorworks-artig): schwebender Knopf oben links über
|
||||
dem Grundriss, sichtbar nur solange eine Gruppe betreten ist (Isolation). */
|
||||
.plan-leave-group-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 20;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--accent);
|
||||
background: var(--field-bg);
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
|
||||
/* Gruppen-Isolation (Vectorworks „Gruppe betreten"): sichtbarer Rahmen um
|
||||
die ganze Ansicht, solange eine Gruppe betreten ist — dieselbe visuelle
|
||||
Sprache wie Vectorworks' "Ansichtsbereich"-Rahmen. */
|
||||
.pane-isolated {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.plan-leave-group-btn:hover {
|
||||
|
||||
/* "Gruppe verlassen": Knopf oben RECHTS in der Ansicht (analog `.pane-title`
|
||||
oben links), sichtbar nur solange eine Gruppe betreten ist. */
|
||||
.pane-leave-group-btn {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
z-index: 3;
|
||||
padding: 5px 11px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--accent-border);
|
||||
background: var(--accent-dim);
|
||||
color: var(--muted);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.pane-leave-group-btn:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Auswahl-Rechteck (Marquee, Links-Drag): gestrichelte Akzentkontur, leichte
|
||||
|
||||
@@ -88,6 +88,7 @@ export function Content({
|
||||
selectedStairIds,
|
||||
selectedExtrudedSolidIds,
|
||||
selectedColumnIds,
|
||||
selectedGroupIds,
|
||||
selectedRoomIds,
|
||||
selectedRoofIds,
|
||||
selectedContextObjectIds,
|
||||
@@ -98,6 +99,7 @@ export function Content({
|
||||
onEditDrawingText,
|
||||
onEnterGroup,
|
||||
groupPath,
|
||||
onLeaveGroup,
|
||||
editTextId,
|
||||
editTextMinHeightM,
|
||||
onCommitTextEdit,
|
||||
@@ -183,6 +185,7 @@ export function Content({
|
||||
selectedStairIds: string[];
|
||||
selectedExtrudedSolidIds: string[];
|
||||
selectedColumnIds: string[];
|
||||
selectedGroupIds: string[];
|
||||
selectedRoomIds: string[];
|
||||
selectedRoofIds: string[];
|
||||
selectedContextObjectIds: string[];
|
||||
@@ -193,6 +196,7 @@ export function Content({
|
||||
onEditDrawingText?: (drawingId: string) => void;
|
||||
onEnterGroup?: (hit: GroupableHit) => boolean;
|
||||
groupPath: string[];
|
||||
onLeaveGroup?: () => void;
|
||||
editTextId?: string | null;
|
||||
editTextMinHeightM?: number | null;
|
||||
onCommitTextEdit?: (doc: RichTextDoc) => void;
|
||||
@@ -315,6 +319,7 @@ export function Content({
|
||||
selectedStairIds={selectedStairIds}
|
||||
selectedExtrudedSolidIds={selectedExtrudedSolidIds}
|
||||
selectedColumnIds={selectedColumnIds}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
selectedRoomIds={selectedRoomIds}
|
||||
selectedRoofIds={selectedRoofIds}
|
||||
selectedSectionLineId={selectedSectionLineId}
|
||||
@@ -324,6 +329,7 @@ export function Content({
|
||||
onEditDrawingText={onEditDrawingText}
|
||||
onEnterGroup={onEnterGroup}
|
||||
groupPath={groupPath}
|
||||
onLeaveGroup={onLeaveGroup}
|
||||
editTextId={editTextId}
|
||||
editTextMinHeightM={editTextMinHeightM}
|
||||
onCommitTextEdit={onCommitTextEdit}
|
||||
@@ -382,6 +388,7 @@ export function Content({
|
||||
selectedStairIds={selectedStairIds}
|
||||
selectedExtrudedSolidIds={selectedExtrudedSolidIds}
|
||||
selectedColumnIds={selectedColumnIds}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
selectedRoomIds={selectedRoomIds}
|
||||
selectedRoofIds={selectedRoofIds}
|
||||
selectedContextObjectIds={selectedContextObjectIds}
|
||||
@@ -392,6 +399,7 @@ export function Content({
|
||||
onEditDrawingText={onEditDrawingText}
|
||||
onEnterGroup={onEnterGroup}
|
||||
groupPath={groupPath}
|
||||
onLeaveGroup={onLeaveGroup}
|
||||
editTextId={editTextId}
|
||||
editTextMinHeightM={editTextMinHeightM}
|
||||
onCommitTextEdit={onCommitTextEdit}
|
||||
@@ -492,6 +500,7 @@ export function Content({
|
||||
selectedStairIds={selectedStairIds}
|
||||
selectedExtrudedSolidIds={selectedExtrudedSolidIds}
|
||||
selectedColumnIds={selectedColumnIds}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
selectedRoomIds={selectedRoomIds}
|
||||
selectedRoofIds={selectedRoofIds}
|
||||
selectedContextObjectIds={selectedContextObjectIds}
|
||||
@@ -502,6 +511,7 @@ export function Content({
|
||||
onEditDrawingText={onEditDrawingText}
|
||||
onEnterGroup={onEnterGroup}
|
||||
groupPath={groupPath}
|
||||
onLeaveGroup={onLeaveGroup}
|
||||
editTextId={editTextId}
|
||||
editTextMinHeightM={editTextMinHeightM}
|
||||
onCommitTextEdit={onCommitTextEdit}
|
||||
@@ -550,6 +560,7 @@ function LevelPlanView({
|
||||
selectedStairIds,
|
||||
selectedExtrudedSolidIds,
|
||||
selectedColumnIds,
|
||||
selectedGroupIds,
|
||||
selectedRoomIds,
|
||||
selectedRoofIds,
|
||||
selectedContextObjectIds,
|
||||
@@ -560,6 +571,7 @@ function LevelPlanView({
|
||||
onEditDrawingText,
|
||||
onEnterGroup,
|
||||
groupPath,
|
||||
onLeaveGroup,
|
||||
editTextId,
|
||||
editTextMinHeightM,
|
||||
onCommitTextEdit,
|
||||
@@ -601,6 +613,7 @@ function LevelPlanView({
|
||||
selectedStairIds: string[];
|
||||
selectedExtrudedSolidIds: string[];
|
||||
selectedColumnIds: string[];
|
||||
selectedGroupIds: string[];
|
||||
selectedRoomIds: string[];
|
||||
selectedRoofIds: string[];
|
||||
selectedContextObjectIds?: string[];
|
||||
@@ -611,6 +624,7 @@ function LevelPlanView({
|
||||
onEditDrawingText?: (drawingId: string) => void;
|
||||
onEnterGroup?: (hit: GroupableHit) => boolean;
|
||||
groupPath: string[];
|
||||
onLeaveGroup?: () => void;
|
||||
editTextId?: string | null;
|
||||
editTextMinHeightM?: number | null;
|
||||
onCommitTextEdit?: (doc: RichTextDoc) => void;
|
||||
@@ -683,12 +697,22 @@ function LevelPlanView({
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="pane">
|
||||
<section className={groupPath.length > 0 ? "pane pane-isolated" : "pane"}>
|
||||
<div className="pane-title">
|
||||
{isDrawing
|
||||
? t("content.drawingTitle", { name: level.name })
|
||||
: t("content.grundrissTitle", { name: level.name })}
|
||||
</div>
|
||||
{groupPath.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="pane-leave-group-btn"
|
||||
onClick={onLeaveGroup}
|
||||
title={t("group.leave.hint")}
|
||||
>
|
||||
{t("group.leave")}
|
||||
</button>
|
||||
)}
|
||||
<PlanView
|
||||
ref={planViewRef}
|
||||
plan={plan}
|
||||
@@ -701,6 +725,7 @@ function LevelPlanView({
|
||||
selectedStairIds={selectedStairIds}
|
||||
selectedExtrudedSolidIds={selectedExtrudedSolidIds}
|
||||
selectedColumnIds={selectedColumnIds}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
selectedRoomIds={selectedRoomIds}
|
||||
selectedRoofIds={selectedRoofIds}
|
||||
selectedContextObjectIds={selectedContextObjectIds}
|
||||
@@ -768,6 +793,7 @@ function SectionPlanView({
|
||||
selectedStairIds,
|
||||
selectedExtrudedSolidIds,
|
||||
selectedColumnIds,
|
||||
selectedGroupIds,
|
||||
selectedRoomIds,
|
||||
selectedRoofIds,
|
||||
selectedSectionLineId,
|
||||
@@ -777,6 +803,7 @@ function SectionPlanView({
|
||||
onEditDrawingText,
|
||||
onEnterGroup,
|
||||
groupPath,
|
||||
onLeaveGroup,
|
||||
editTextId,
|
||||
editTextMinHeightM,
|
||||
onCommitTextEdit,
|
||||
@@ -814,6 +841,7 @@ function SectionPlanView({
|
||||
selectedStairIds: string[];
|
||||
selectedExtrudedSolidIds: string[];
|
||||
selectedColumnIds: string[];
|
||||
selectedGroupIds: string[];
|
||||
selectedRoomIds: string[];
|
||||
selectedRoofIds: string[];
|
||||
selectedSectionLineId: string | null;
|
||||
@@ -823,6 +851,7 @@ function SectionPlanView({
|
||||
onEditDrawingText?: (drawingId: string) => void;
|
||||
onEnterGroup?: (hit: GroupableHit) => boolean;
|
||||
groupPath: string[];
|
||||
onLeaveGroup?: () => void;
|
||||
editTextId?: string | null;
|
||||
editTextMinHeightM?: number | null;
|
||||
onCommitTextEdit?: (doc: RichTextDoc) => void;
|
||||
@@ -921,10 +950,20 @@ function SectionPlanView({
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="pane">
|
||||
<section className={groupPath.length > 0 ? "pane pane-isolated" : "pane"}>
|
||||
<div className="pane-title">
|
||||
{t("content.sectionTitle", { name: level.name, kind: kindLabel })}
|
||||
</div>
|
||||
{groupPath.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="pane-leave-group-btn"
|
||||
onClick={onLeaveGroup}
|
||||
title={t("group.leave.hint")}
|
||||
>
|
||||
{t("group.leave")}
|
||||
</button>
|
||||
)}
|
||||
<PlanView
|
||||
ref={planViewRef}
|
||||
plan={plan}
|
||||
@@ -937,6 +976,7 @@ function SectionPlanView({
|
||||
selectedStairIds={selectedStairIds}
|
||||
selectedExtrudedSolidIds={selectedExtrudedSolidIds}
|
||||
selectedColumnIds={selectedColumnIds}
|
||||
selectedGroupIds={selectedGroupIds}
|
||||
selectedRoomIds={selectedRoomIds}
|
||||
selectedRoofIds={selectedRoofIds}
|
||||
selectedSectionLineIds={selectedSectionLineId ? [selectedSectionLineId] : []}
|
||||
|
||||
Reference in New Issue
Block a user