From 84b28ba12cd69dd63802bbd7e0c7a565f48aca4e Mon Sep 17 00:00:00 2001 From: CODEX Date: Thu, 18 Jun 2026 22:09:55 +0300 Subject: [PATCH] Refine BIM section controls and styling --- frontend/dcViewer.css | 80 ++++++++++++----------- frontend/dcViewer.js | 84 ++++++++++++++++++++++++- frontend/index.html | 14 ++++- frontend/lib/dc-camera-context.es.js | 94 ++++++++++++++++++++++++++-- frontend/src/ui/settingsMenu.js | 60 ++++++++++++++++++ frontend/theme.js | 8 +++ 6 files changed, 294 insertions(+), 46 deletions(-) diff --git a/frontend/dcViewer.css b/frontend/dcViewer.css index 0f8e02b..341444c 100644 --- a/frontend/dcViewer.css +++ b/frontend/dcViewer.css @@ -51,6 +51,16 @@ --measure-label: #c4ff67; --measure-label-text: #292929; --measure-dot: #c4ff67; + --section-axis-x: #ff2b2b; + --section-axis-y: #35ff4f; + --section-axis-z: #285bff; + --section-gizmo-diameter: 1; + --section-plane-fill: #606060; + --section-plane-alpha: 0.32; + --section-plane-edge: #0d0d0d; + --section-plane-edge-alpha: 0.8; + --section-toolbar-left: calc(12px + var(--tb-size, 25px) + var(--tb-gap, 10px)); + --section-toolbar-bottom: 123px; --nodedc-glass-control-bg: var(--modal-element); --nodedc-glass-control-hover: color-mix(in srgb, var(--modal-element) 86%, white 8%); --nodedc-glass-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.01); @@ -1928,19 +1938,20 @@ body[data-viewer-mode="guest"] .inspector-restore-tray { .section-toolbar { position: fixed; - left: calc(12px + var(--tb-size, 52px) + 10px); - bottom: 12px; + left: var(--section-toolbar-left); + bottom: var(--section-toolbar-bottom); z-index: 16; display: flex; align-items: center; - gap: 6px; - padding: 6px; - border: 1px solid var(--toolbar-border); - border-radius: var(--tb-radius, 12px); - background: color-mix(in srgb, var(--toolbar-bg) 88%, transparent); - box-shadow: 0 18px 34px rgba(0, 0, 0, 0.28); - backdrop-filter: blur(18px) saturate(124%); - -webkit-backdrop-filter: blur(18px) saturate(124%); + gap: var(--tb-gap, 10px); + height: var(--tb-size, 25px); + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; + backdrop-filter: none; + -webkit-backdrop-filter: none; } .section-toolbar.hidden { @@ -1948,26 +1959,30 @@ body[data-viewer-mode="guest"] .inspector-restore-tray { } .section-tool-btn { - width: 38px; - height: 38px; + width: var(--tb-size, 25px); + height: var(--tb-size, 25px); display: inline-grid; place-items: center; - border: 1px solid transparent; - border-radius: calc(var(--tb-radius, 12px) - 4px); - background: transparent; + border: 1px solid var(--toolbar-border); + border-radius: var(--tb-radius, 7px); + background: var(--toolbar-bg); color: var(--text); - font-size: 13px; + font-size: var(--tb-icon-size, 13px); font-weight: 700; line-height: 1; cursor: pointer; - transition: background 120ms ease, border-color 120ms ease, transform 100ms ease; + padding: var(--tb-icon-pad, 5px); + transition: transform 120ms ease, border-color 120ms ease, box-shadow 120ms ease, background 120ms ease; } -.section-tool-btn:hover, -.section-tool-btn.active { +.section-tool-btn:hover { + transform: translateY(-1px); border-color: var(--toolbar-outline, rgba(76, 224, 210, 0.7)); + box-shadow: 0 10px 20px color-mix(in srgb, var(--toolbar-outline, rgba(76, 224, 210, 0.7)) 30%, transparent); +} + +.section-tool-btn.active { background: var(--toolbar-bg-active); - color: var(--modal-btn-primary-contrast, #15170f); } .section-tool-btn:active { @@ -1976,8 +1991,8 @@ body[data-viewer-mode="guest"] .inspector-restore-tray { .section-tool-btn svg { display: block; - width: 21px; - height: 21px; + width: var(--tb-svg-size, 24px); + height: var(--tb-svg-size, 24px); fill: none; stroke: currentColor; stroke-width: 1.8; @@ -1990,21 +2005,14 @@ body[data-viewer-mode="guest"] .inspector-restore-tray { } .section-overview { - position: fixed; - left: calc(12px + var(--tb-size, 52px) + 10px); - bottom: 66px; - width: 150px; - height: 150px; - z-index: 14; - display: none; - border: 1px solid var(--toolbar-border); - border-radius: 12px; - background: color-mix(in srgb, var(--toolbar-bg) 84%, transparent); - box-shadow: 0 18px 34px rgba(0, 0, 0, 0.3); -} - -body[data-section-mode="active"] .section-overview { + width: var(--tb-size, 25px); + height: var(--tb-size, 25px); + flex: 0 0 var(--tb-size, 25px); display: block; + border: 1px solid var(--toolbar-border); + border-radius: var(--tb-radius, 7px); + background: var(--toolbar-bg); + box-shadow: none; } .templates-panel { diff --git a/frontend/dcViewer.js b/frontend/dcViewer.js index 0dc9798..cc36337 100644 --- a/frontend/dcViewer.js +++ b/frontend/dcViewer.js @@ -1,10 +1,10 @@ const SDK_VERSION = "2.6.78"; // соответствует package.json -const SDK_URL = "./lib/dc-camera-context.es.js"; +const SDK_URL = "./lib/dc-camera-context.es.js?v=2"; import { createLogo } from "./src/ui/logo.js"; import { toolbarConfig, applyToolbarVars } from "./src/ui/toolbar.js"; import { createTemplatesMenu } from "./src/ui/templatesMenu.js?v=2"; -import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=5"; +import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=6"; import { createMeasurementModal } from "./src/ui/measurementModal.js"; import { createGlassSelect } from "./src/ui/glassSelect.js"; import { createSliderControl } from "./src/ui/controls/slider.js"; @@ -943,6 +943,12 @@ const cssValue = (name, fallback = "") => { return (v && v.trim()) || fallback; }; +const clampNumber = (value, min, max, fallback) => { + const num = Number(value); + if (!Number.isFinite(num)) return fallback; + return Math.max(min, Math.min(max, num)); +}; + const hexToRgb01 = (hex) => { if (!hex) return [1, 1, 1]; const clean = hex.trim(); @@ -998,6 +1004,56 @@ const applyMeasureTheme = () => { }); }; +const sectionPlaneThemeFromCSS = () => ({ + axes: { + x: hexToRgb01(cssValue("--section-axis-x", "#ff2b2b")), + y: hexToRgb01(cssValue("--section-axis-y", "#35ff4f")), + z: hexToRgb01(cssValue("--section-axis-z", "#285bff")), + }, + gizmoDiameter: clampNumber(parseFloat(cssValue("--section-gizmo-diameter", "1")), 0.5, 1.8, 1), + planeFillColor: hexToRgb01(cssValue("--section-plane-fill", "#606060")), + planeFillAlpha: clampNumber(parseFloat(cssValue("--section-plane-alpha", "0.32")), 0, 1, 0.32), + planeEdgeColor: hexToRgb01(cssValue("--section-plane-edge", "#0d0d0d")), + planeEdgeAlpha: clampNumber(parseFloat(cssValue("--section-plane-edge-alpha", "0.8")), 0, 1, 0.8), +}); + +const applySectionPlaneTheme = () => { + if (!sectionPlanesPlugin) return; + const theme = sectionPlaneThemeFromCSS(); + Object.values(sectionPlanesPlugin._controls || {}).forEach((control) => { + control?.setStyle?.(theme); + }); + Object.values(sectionPlanesPlugin._overview?._planes || {}).forEach((plane) => { + const mesh = plane?._mesh; + if (!mesh) return; + if (mesh.material) { + mesh.material.diffuse = theme.planeFillColor; + mesh.material.emissive = theme.planeFillColor; + mesh.material.alpha = theme.planeFillAlpha; + mesh.material.alphaMode = theme.planeFillAlpha < 1 ? "blend" : "opaque"; + } + if (mesh.edgeMaterial) { + mesh.edgeMaterial.edgeColor = theme.planeEdgeColor; + mesh.edgeMaterial.edgeAlpha = theme.planeEdgeAlpha; + } + if (mesh.highlightMaterial) { + mesh.highlightMaterial.fillColor = theme.planeFillColor; + mesh.highlightMaterial.fillAlpha = theme.planeFillAlpha; + mesh.highlightMaterial.edgeColor = theme.planeEdgeColor; + mesh.highlightMaterial.edgeAlpha = theme.planeEdgeAlpha; + } + if (mesh.selectedMaterial) { + mesh.selectedMaterial.fillColor = theme.planeFillColor; + mesh.selectedMaterial.fillAlpha = theme.planeFillAlpha; + mesh.selectedMaterial.edgeColor = theme.planeEdgeColor; + mesh.selectedMaterial.edgeAlpha = theme.planeEdgeAlpha; + } + mesh.opacity = theme.planeFillAlpha; + }); + viewer?.scene?.glRedraw?.(); + sectionPlanesPlugin._overview?._scene?.glRedraw?.(); +}; + const applyHighlightTheme = () => { if (!viewer?.scene?.highlightMaterial) return; const hexToRGB = (hex) => { @@ -1229,8 +1285,21 @@ const setSectionAxisActive = (axis) => { }); }; +const positionSectionToolbar = () => { + if (!sectionButton || !sectionToolbar) return; + const rect = sectionButton.getBoundingClientRect(); + if (!rect.width || !rect.height) return; + const gap = parseFloat(cssValue("--tb-gap", "10")) || 10; + const root = document.documentElement; + root.style.setProperty("--section-toolbar-left", `${Math.round(rect.right + gap)}px`); + root.style.setProperty("--section-toolbar-bottom", `${Math.round(Math.max(8, window.innerHeight - rect.bottom))}px`); +}; + const setSectionModeActive = (active) => { sectionModeActive = !!active && !!sectionPlanesPlugin && getSectionPlaneIds().length > 0 && !isGuestMode(); + if (sectionModeActive) { + positionSectionToolbar(); + } sectionButton?.classList.toggle("active", sectionModeActive); sectionToolbar?.classList.toggle("hidden", !sectionModeActive); document.body.dataset.sectionMode = sectionModeActive ? "active" : ""; @@ -1287,6 +1356,7 @@ const createOrUpdateSectionPlane = (axis = "camera") => { activeSectionPlaneId = sectionPlane.id || activeSectionPlaneId; } + applySectionPlaneTheme(); sectionPlanesPlugin.showControl(activeSectionPlaneId); setSectionModeActive(true); setSectionAxisActive(axis); @@ -1569,6 +1639,11 @@ window.addEventListener("pointerup", scheduleCameraRearm, true); window.addEventListener("mouseup", scheduleCameraRearm, true); window.addEventListener("touchend", scheduleCameraRearm, true); window.addEventListener("touchcancel", scheduleCameraRearm, true); +window.addEventListener("resize", () => { + if (sectionModeActive) { + positionSectionToolbar(); + } +}); document.addEventListener("visibilitychange", () => { if (!document.hidden) { scheduleCameraRearm(); @@ -3099,6 +3174,7 @@ const initViewer = async () => { overviewVisible: false, }); sectionPlanesPlugin.setOverviewVisible(false); + applySectionPlaneTheme(); setSectionModeActive(false); } else { console.warn("SectionPlanesPlugin is not available in the loaded SDK build"); @@ -3971,6 +4047,9 @@ const loadProjectSnapshot = async (project) => { if (key && key.startsWith("measure")) { applyMeasureTheme(); } + if (key && key.startsWith("section")) { + applySectionPlaneTheme(); + } focusCanvasAndControls(); }, onShow: () => { @@ -3986,6 +4065,7 @@ const loadProjectSnapshot = async (project) => { rebuildNavCube(sdk); applyHighlightTheme(); applyMeasureTheme(); + applySectionPlaneTheme(); if (displayModeControl && displayModeSelectHost) { displayModeSelect = createGlassSelect({ host: displayModeSelectHost, diff --git a/frontend/index.html b/frontend/index.html index c2061fe..9176de5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -81,6 +81,14 @@ set("--measure-label", s.measureLabel); set("--measure-label-text", s.measureLabelText); set("--measure-dot", s.measureDot); + set("--section-axis-x", s.sectionAxisX); + set("--section-axis-y", s.sectionAxisY); + set("--section-axis-z", s.sectionAxisZ); + set("--section-gizmo-diameter", s.sectionGizmoDiameter); + set("--section-plane-fill", s.sectionPlaneFill); + set("--section-plane-alpha", s.sectionPlaneAlpha); + set("--section-plane-edge", s.sectionPlaneEdge); + set("--section-plane-edge-alpha", s.sectionPlaneEdgeAlpha); } catch (e) { // ignore corrupted settings } @@ -89,7 +97,7 @@ - +
@@ -107,7 +115,6 @@
-
+
@@ -318,6 +325,7 @@