2D-Plan-Renderer auf WebGL2 (GPU) + akkumulierter Funktionsstand

Neuer GPU-Renderer fuer den Grundriss (src/plan/glPlan/): Earcut-Tessellierung
(konkav-faehig), gehrte Linienzuege (Miter), echte Papier-mm-Strichbreiten im
Massstab (repliziert den SVG-printStrokeVb-Pfad), Hybrid mit scharfem SVG-Text-
Overlay. GPU ist der Standardpfad; der SVG-Renderer bleibt automatischer Fallback,
falls WebGL2/Shader nicht verfuegbar sind. Imperativer Pan (rAF + CSS-transform)
fuer fluessige Interaktion ohne React-Re-Render je Frame.

Enthaelt zudem den bisher nicht committeten Arbeitsstand des Browser-BIM
(Oeffnungen, Treppen, Raeume, Decken, DXF-Export, Materialbibliothek, Kontext-
Import, Tauri-Compute-Boundary-PoC).
This commit is contained in:
2026-07-02 00:12:39 +02:00
parent cfe5249440
commit 3d2d4d6321
184 changed files with 29421 additions and 669 deletions
+267
View File
@@ -48,6 +48,10 @@ export const en: Record<TranslationKey, string> = {
"topbar.fit.hint": "Fit to plan",
"topbar.fitSelection": "Selection",
"topbar.fitSelection.hint": "Fit to selection (otherwise to plan)",
"topbar.exportPdf": "PDF",
"topbar.exportPdf.hint": "Export the floor plan as a vector PDF",
"topbar.exportDxf": "DXF",
"topbar.exportDxf.hint": "Export the floor plan as DXF (model space in meters)",
"topbar.zoom.current": "Current zoom",
"topbar.renderGroup": "Render mode",
@@ -67,6 +71,7 @@ export const en: Record<TranslationKey, string> = {
"display.style.mono": "Black & white",
"display.style.shaded": "Shaded",
"display.style.white": "White",
"display.style.textured": "Textured",
"display.style.wireframe": "Wireframe",
"display.style.hidden": "Hidden line",
@@ -102,12 +107,23 @@ export const en: Record<TranslationKey, string> = {
"tool.group": "Tools",
"tool.select": "Select",
"tool.wall": "Wall",
"tool.ceiling": "Ceiling",
"tool.window": "Window",
"tool.door": "Door",
"tool.stair": "Stair",
"tool.line": "Line",
"tool.polyline": "Polyline",
"tool.rect": "Rectangle",
"tool.select.hint": "Select — click/drag elements",
"tool.window.hint": "Window: pick a wall, then set the position",
"tool.door.hint": "Door: pick a wall, then set the position",
"tool.stair.hint": "Stair: set start point, then run direction/end",
"tool.room": "Room",
"tool.room.hint": "Room: click inside an enclosed area (or draw an outline)",
"tool.wall.firstPoint": "Wall: set first axis point",
"tool.wall.nextPoint": "Wall: next point — double/right-click to finish",
"tool.ceiling.firstPoint": "Ceiling: set first outline point",
"tool.ceiling.nextPoint": "Ceiling: next point — click start to close, Enter finishes",
"tool.line.firstPoint": "Line: set start point",
"tool.line.secondPoint": "Line: set end point",
"tool.polyline.firstPoint": "Polyline: set first point",
@@ -115,6 +131,7 @@ export const en: Record<TranslationKey, string> = {
"tool.rect.firstCorner": "Rectangle: set first corner",
"tool.rect.secondCorner": "Rectangle: set opposite corner",
"tool.wallType": "Wall type",
"tool.ceilingType": "Ceiling type",
"tool.layer": "Layer",
"tool.layer.hint": "Active layer — everything drawn goes onto this category",
"tool.floorOnlyDisabled": "Walls can only be drawn on a floor",
@@ -208,8 +225,12 @@ export const en: Record<TranslationKey, string> = {
"objinfo.height": "Height",
"objinfo.type": "Type",
"objinfo.kind.wall": "Wall",
"objinfo.kind.ceiling": "Ceiling",
"objinfo.kind.drawing2d": "2D object",
"objinfo.kind.door": "Door",
"objinfo.kind.opening": "Opening",
"objinfo.kind.stair": "Stair",
"objinfo.kind.room": "Room",
// ── Wall attributes (object info panel) ──────────────────────────────────
"objinfo.wall.section": "Wall",
"objinfo.wall.refLine": "Reference line",
@@ -230,6 +251,64 @@ export const en: Record<TranslationKey, string> = {
"objinfo.wall.anchorCustom": "Custom height",
"objinfo.wall.height": "Height",
"objinfo.wall.floorAboveNone": "No floor above",
// ── Ceiling attributes (object info panel) ───────────────────────────────
"objinfo.ceiling.section": "Ceiling",
"objinfo.ceiling.buildup": "Build-up",
"objinfo.ceiling.thickness": "Thickness",
"objinfo.ceiling.preset": "Ceiling style",
"objinfo.ceiling.refFloor": "Reference floor",
"objinfo.ceiling.top": "Top (OK)",
"objinfo.ceiling.bottom": "Bottom (UK)",
"objinfo.ceiling.height": "Thickness (OKUK)",
"objinfo.ceiling.area": "Area",
"objinfo.ceiling.anchorFloor": "Bound to floor",
"objinfo.ceiling.anchorCustom": "Custom height",
// ── Opening attributes (object info panel) ──────────────────────────────
"objinfo.opening.section": "Opening",
"objinfo.opening.kind": "Type",
"objinfo.opening.kind.window": "Window",
"objinfo.opening.kind.door": "Door",
"objinfo.opening.hostWall": "Host wall",
"objinfo.opening.position": "Position (from wall start)",
"objinfo.opening.width": "Width",
"objinfo.opening.height": "Height",
"objinfo.opening.sill": "Sill height",
"objinfo.opening.swingAngle": "Swing angle",
"objinfo.opening.hinge": "Hinge",
"objinfo.opening.hinge.start": "Start",
"objinfo.opening.hinge.end": "End",
"objinfo.opening.swing": "Swing side",
"objinfo.opening.swing.left": "Left",
"objinfo.opening.swing.right": "Right",
"objinfo.opening.dir": "Direction",
"objinfo.opening.dir.in": "In",
"objinfo.opening.dir.out": "Out",
"objinfo.opening.bottom": "Bottom (UK)",
"objinfo.opening.top": "Top (OK)",
"objinfo.stair.section": "Stair",
"objinfo.stair.shape": "Shape",
"objinfo.stair.shape.straight": "Straight",
"objinfo.stair.shape.L": "L-shaped",
"objinfo.stair.shape.spiral": "Spiral",
"objinfo.stair.width": "Run width",
"objinfo.stair.steps": "Step count",
"objinfo.stair.rise": "Total rise",
"objinfo.stair.riser": "Riser height",
"objinfo.stair.tread": "Tread depth",
"objinfo.stair.direction": "Run direction",
"objinfo.stair.direction.up": "Upwards",
"objinfo.stair.direction.down": "Downwards",
"objinfo.stair.refFloor": "Reference floor",
"objinfo.stair.bottom": "Bottom (UK)",
"objinfo.stair.top": "Top (OK)",
// ── Room attributes (object info panel) ──────────────────────────────────
"objinfo.room.section": "Room",
"objinfo.room.name": "Name",
"objinfo.room.sia": "SIA category",
"objinfo.room.area": "Area",
"objinfo.room.perimeter": "Perimeter",
"objinfo.room.refFloor": "Reference floor",
"objinfo.room.editStamp": "Edit stamp …",
"levels.addFloor": "+ Floor",
"levels.addDrawing": "+ Drawing",
"layers.addCategory": "+ Layer",
@@ -279,6 +358,11 @@ export const en: Record<TranslationKey, string> = {
"ctx.fitSelection": "Fit to selection",
"ctx.clearSelection": "Clear selection",
"ctx.fit": "Fit view",
"ctx.union": "Union",
"ctx.subtract": "Subtract",
"ctx.intersect": "Intersect",
"ctx.boolean.disabled":
"Select at least two closed areas (rectangle or closed polyline).",
"ctx.noWallHit": "No wall hit",
"ctx.noWallHit.hint": "Right-clicking a wall selects it and shows wall actions.",
@@ -355,8 +439,47 @@ export const en: Record<TranslationKey, string> = {
"resources.col.prio": "Prio",
"resources.col.prio.hint": "Higher priority runs through at the join",
"resources.col.texture": "Texture",
"resources.col.material": "Material",
"resources.delete.component": "Delete component “{name}”",
// 3D material (PBR) — built-in library + upload.
"material.assign": "Material …",
"material.none": "— none —",
"material.dialog.title": "Assign 3D material",
"material.dialog.hint": "Pick a built-in material or upload your own textures. Applies in the 3D “Textured” mode.",
"material.library": "Library (ambientCG, CC0)",
"material.upload": "Own textures",
"material.upload.hint": "Upload images as maps (at least color). Size = physical tile size in meters.",
"material.upload.color": "Color (albedo)",
"material.upload.normal": "Normal",
"material.upload.roughness": "Roughness",
"material.upload.displacement": "Height",
"material.upload.ao": "AO",
"material.upload.choose": "Image …",
"material.upload.apply": "Assign",
"material.size": "Tile size (m)",
"material.current": "Current",
"material.clear": "Remove material",
"material.close": "Close",
"material.uploaded": "Uploaded",
// ── Browse ambientCG library (live) ──────────────────────────────────────
"material.browse": "Browse library",
"material.browse.starter": "Quick pick",
"material.browse.hint":
"Search the full CC0 ambientCG library (~2000+ materials). One click downloads the textures at runtime and assigns them.",
"material.browse.search": "Search …",
"material.browse.searchPlaceholder": "e.g. wood, concrete, tiles …",
"material.browse.category.all": "All categories",
"material.browse.resolution": "Resolution",
"material.browse.more": "Load more",
"material.browse.loading": "Loading …",
"material.browse.downloading": "Downloading texture …",
"material.browse.empty": "No materials found.",
"material.browse.count": "{shown} of {total}",
"material.browse.error":
"Library unreachable. A proxy for ambientCG is required at runtime (dev: Vite proxy active; prod: set VITE_AMBIENTCG_PROXY).",
"material.browse.downloadError": "Download failed (check CORS/proxy).",
"resources.hatches.hint": "Pattern, scale and angle drive the section hatch; the line style sets the weight of the pattern lines.",
"resources.hatches.empty": "No hatches yet.",
"resources.hatches.placeholder": "Hatch",
@@ -390,6 +513,30 @@ export const en: Record<TranslationKey, string> = {
"cmd.aria": "Command line",
"cmd.idle.prompt": "Command:",
"cmd.idle.placeholder": "Type a command … (Tab)",
"cmd.wall.label": "Wall",
"cmd.wall.start": "Start of wall:",
"cmd.wall.next": "Next point:",
"cmd.ceiling.label": "Ceiling",
"cmd.ceiling.start": "First outline point of ceiling:",
"cmd.ceiling.next": "Next point ( Close ):",
"cmd.opening.label": "Opening",
"cmd.opening.windowLabel": "Window",
"cmd.opening.doorLabel": "Door",
"cmd.opening.wall": "Pick a wall for the opening:",
"cmd.opening.pos": "Position along the wall ( type distance ):",
"cmd.opening.kind": "Type",
"cmd.opening.hinge": "Hinge",
"cmd.opening.side": "Side",
"cmd.opening.dir": "Direction",
"cmd.opening.sillField": "Sill",
"cmd.opening.swingField": "Angle",
"cmd.stair.label": "Stair",
"cmd.stair.start": "Start point of the stair:",
"cmd.stair.run": "Run direction / end ( Shape · Up/Down ):",
"cmd.stair.shape": "Shape",
"cmd.stair.updown": "Up/Down",
"cmd.stair.stepsField": "Steps",
"cmd.stair.riseField": "Rise",
"cmd.line.label": "Line",
"cmd.line.start": "Start of line:",
"cmd.line.end": "End of line:",
@@ -398,9 +545,15 @@ export const en: Record<TranslationKey, string> = {
"cmd.polyline.next": "Next point:",
"cmd.polyline.close": "Close",
"cmd.polyline.undo": "Undo",
"cmd.polyline.closedOpt": "Closed",
"cmd.rect.label": "Rectangle",
"cmd.rect.first": "First corner:",
"cmd.rect.second": "Opposite corner:",
"cmd.rect.method": "Method",
"cmd.rect.center": "Center:",
"cmd.rect.corner": "Corner:",
"cmd.rect.baseEnd": "Second corner (base edge):",
"cmd.rect.rise": "Height:",
"cmd.circle.label": "Circle",
"cmd.circle.center": "Center:",
"cmd.circle.radius": "Radius:",
@@ -408,6 +561,11 @@ export const en: Record<TranslationKey, string> = {
"cmd.move.base": "Base point:",
"cmd.move.target": "Target point:",
"cmd.move.empty": "Nothing selected (select objects first).",
"cmd.mirror.label": "Mirror",
"cmd.mirror.first": "First point of mirror axis:",
"cmd.mirror.second": "Second point of mirror axis:",
"cmd.mirror.empty": "Nothing selected (select objects first).",
"cmd.join.prompt": "Joining selected elements …",
"cmd.copy.label": "Copy",
"cmd.copy.base": "Base point:",
"cmd.copy.target": "Target point (Enter to finish):",
@@ -415,6 +573,9 @@ export const en: Record<TranslationKey, string> = {
"cmd.offset.label": "Offset",
"cmd.offset.pick": "Select curve:",
"cmd.offset.side": "Side or distance:",
// Trim (quick-trim): click the part to cut away; repeats until Esc.
"cmd.trim.label": "Trim",
"cmd.trim.pick": "Click the part to trim away (Esc to finish):",
// Edit tools Split / Join / Delete segment (keyboard: Ctrl+S / Ctrl+J /
// Alt+Click). Labels for the command line / menus.
"edit.split.label": "Split",
@@ -427,6 +588,8 @@ export const en: Record<TranslationKey, string> = {
"cmd.field.height": "Height",
"cmd.field.radius": "Radius",
"cmd.field.distance": "Distance",
"cmd.field.thickness": "Thickness",
"cmd.edit.point": "Drag grip (Tab: length/angle):",
// Commands: import / terrain.
"cmd.import.label": "Import",
@@ -434,6 +597,17 @@ export const en: Record<TranslationKey, string> = {
"cmd.terrain.label": "Terrain",
"cmd.terrain.prompt": "Generate terrain from contours …",
// Area balance (SIA 416).
"nav.roomBalance": "Area balance",
"balance.title": "Area balance (SIA 416)",
"balance.rooms": "Rooms",
"balance.summary": "Balance",
"balance.export": "Export CSV",
"balance.empty": "No rooms yet. Create one with the room tool.",
// Room stamp editor.
"room.stamp.editTitle": "Edit room stamp",
"room.stamp.close": "Close",
// Site / context panel (SitePanel).
"nav.site": "Terrain",
"site.title": "Terrain & Context",
@@ -479,6 +653,28 @@ export const en: Record<TranslationKey, string> = {
"import.badge.elevation": "Elevation",
"import.badge.drawing": "Drawing",
// PDF export.
"export.title": "Export PDF",
"export.close": "Close",
"export.plan": "Plan",
"export.scale": "Scale",
"export.paper": "Paper size",
"export.orientation": "Orientation",
"export.orientation.portrait": "Portrait",
"export.orientation.landscape": "Landscape",
"export.warnFit":
"At the chosen scale the plan does not fully fit the sheet — pick a larger format or a smaller scale.",
"export.confirm": "Export",
"export.cancel": "Cancel",
// DXF export.
"exportDxf.title": "Export DXF",
"exportDxf.hatches": "Hatches",
"exportDxf.hatches.yes": "Include",
"exportDxf.hatches.no": "Outlines only",
"exportDxf.unitsNote":
"Model space in meters (1:1). Layers mirror the project's categories; scale freely on plot.",
// Drag & drop (app-wide file drop).
"drop.hint": "Drop a DXF/DWG file here",
@@ -487,4 +683,75 @@ export const en: Record<TranslationKey, string> = {
"error.title": "Something went wrong",
"error.body": "An unexpected error occurred. Details in the console.",
"error.reload": "Reload",
// Location import (geo.admin / OSM).
"site.importLocation": "Import location",
"ctxImport.title": "Import location",
"ctxImport.close": "Close",
"ctxImport.location": "Place / address",
"ctxImport.searchPlaceholder": "e.g. Bundesplatz Bern",
"ctxImport.search": "Search",
"ctxImport.searching": "Searching…",
"ctxImport.noResults": "No matches.",
"ctxImport.searchError": "Search failed.",
"ctxImport.radius": "Radius",
"ctxImport.sources": "Sources",
"ctxImport.src.swissBuildings": "Swisstopo buildings",
"ctxImport.src.osmBuildings": "OSM buildings",
"ctxImport.src.osmRoads": "OSM roads",
"ctxImport.src.osmWater": "OSM water",
"ctxImport.src.osmGreen": "OSM greenery",
"ctxImport.cancel": "Cancel",
"ctxImport.confirm": "Import",
"ctxImport.importing": "Importing…",
"ctxImport.working": "Loading geodata…",
"ctxImport.empty": "No geometry found in the selected area.",
"ctxImport.importError": "Import failed.",
"ctxImport.imported": "Imported {count} objects in {sets} sets.",
// Room command.
"cmd.room.label": "Room",
"cmd.room.mode": "Mode",
"cmd.room.inside": "Click inside room:",
"cmd.room.first": "Set first room point:",
"cmd.room.next": "Set next point ( Enter/Start closes ):",
// Rich-text editor.
// Text group (top bar).
"text.group": "Text",
"text.style": "Style",
"text.style.none": "— Style —",
"text.font": "Font",
"text.size": "Size",
"text.size.custom": "Custom …",
"text.size.customPrompt": "Font size in pt:",
"text.bold": "Bold",
"text.italic": "Italic",
"text.underline": "Underline",
"text.align.left": "Align left",
"text.align.center": "Center",
"text.align.right": "Align right",
"text.add": "Text",
"text.add.hint": "Text tool — place new text (coming soon)",
"text.editTitle": "Edit text",
"text.apply": "Apply",
"text.cancel": "Cancel",
"roomStamp.title": "Edit room stamp",
"roomStamp.number": "Room number",
"roomStamp.name": "Room name",
"roomStamp.nameLine2": "Room name line 2",
"roomStamp.showFloorArea": "Show floor area",
"roomStamp.floorAreaPrefix": "Floor area prefix",
"roomStamp.showUsage": "Show usage",
"text.selectedHint": "Formatting applies to the selected text",
"rt.bold": "Bold",
"rt.italic": "Italic",
"rt.underline": "Underline",
"rt.strike": "Strikethrough",
"rt.strikethrough": "Strikethrough",
"rt.fontSize": "Font size",
"rt.color": "Color",
"rt.preset": "Preset",
"rt.preset.none": "— Preset —",
};