diff --git a/index.html b/index.html
index 2f98510..b0488b6 100644
--- a/index.html
+++ b/index.html
@@ -39,7 +39,8 @@
href="/src/MeasurementControl/measurementsPanel.css"
/>
-
+
+
diff --git a/src/2DProfileOverride/2DProfileOverride.css b/src/2DProfileOverride/2DProfileOverride.css
new file mode 100644
index 0000000..b14df6d
--- /dev/null
+++ b/src/2DProfileOverride/2DProfileOverride.css
@@ -0,0 +1,12 @@
+/* Keep profile SVG content (axes/labels) from being clipped */
+#profileSVG {
+ overflow: visible !important;
+}
+
+/* Improve axis label readability against varying backgrounds */
+#profile_window .axis text {
+ paint-order: stroke;
+ stroke: #000;
+ stroke-width: 3px;
+ stroke-linejoin: round;
+}
diff --git a/src/2DProfileOverride/2DProfileOverride.js b/src/2DProfileOverride/2DProfileOverride.js
new file mode 100644
index 0000000..721a67c
--- /dev/null
+++ b/src/2DProfileOverride/2DProfileOverride.js
@@ -0,0 +1,224 @@
+import { ecef, wgs84 } from '../config.js'
+
+/**
+ * Initialize runtime overrides for Potree 2D profile behavior.
+ * - Patches ProfileWindow.addPoints: converts each point's Z to elevation instead.
+ * - Rewrites selection info table to show lon/lat/elevation.
+ */
+export function init2DProfileOverride(viewer) {
+ const tryPatchAddPoints = (target) => {
+ if (!target || target.__elevationPatchApplied) return false
+ const originalAddPoints = target.addPoints
+ if (typeof originalAddPoints !== 'function') return false
+
+ target.addPoints = function patchedAddPoints(pointcloud, points) {
+ try {
+ if (!points || !points.data || !points.data.position) {
+ return originalAddPoints.call(this, pointcloud, points)
+ }
+
+ const srcPos = points.data.position
+ // Divide by 3 because positions are [x0,y0,z0, x1,y1,z1, ...]
+ const count = points.numPoints || Math.floor(srcPos.length / 3)
+ const posArray = srcPos.constructor || Float32Array
+
+ // Clone without changing original buffers
+ const cloned = {
+ ...points,
+ data: { ...points.data, position: new posArray(srcPos) }
+ }
+ const dstPos = cloned.data.position
+
+ const pcx = pointcloud?.position?.x || 0
+ const pcy = pointcloud?.position?.y || 0
+ const pcz = pointcloud?.position?.z || 0
+
+ // Preserve world ECEF Z per point (best-effort). Some Potree UIs attach
+ // selectedPoint from these attributes; we keep it on the cloned structure
+ // for later retrieval in the selection panel override.
+ const ecefZWorld = new Float64Array(count)
+
+ for (let i = 0; i < count; i++) {
+ const ix = 3 * i
+ const x = srcPos[ix + 0] + pcx
+ const y = srcPos[ix + 1] + pcy
+ const z = srcPos[ix + 2] + pcz
+
+ const [, , elevation] = proj4(ecef, wgs84, [x, y, z])
+ // Internally, Potree adds pointcloud.position.z back later.
+ dstPos[ix + 2] = elevation - pcz
+
+ ecefZWorld[i] = z
+ }
+
+ cloned.data.ecefZWorld = ecefZWorld
+
+ const result = originalAddPoints.call(this, pointcloud, cloned)
+
+ // Try to tag currently selectedPoint with the original ECEF Z if available
+ try {
+ if (
+ this &&
+ this.selectedPoint &&
+ Number.isFinite(this.selectedPoint.index)
+ ) {
+ const idx = this.selectedPoint.index
+ if (ecefZWorld && idx >= 0 && idx < ecefZWorld.length) {
+ this.selectedPoint.ecefZWorld = ecefZWorld[idx]
+ }
+ }
+ } catch {}
+
+ return result
+ } catch (err) {
+ console.warn(
+ '2DProfileOverride: failed to apply elevation override',
+ err
+ )
+ return originalAddPoints.call(this, pointcloud, points)
+ }
+ }
+
+ target.__elevationPatchApplied = true
+ return true
+ }
+
+ let patched = false
+ if (viewer && viewer.profileWindow) {
+ patched = tryPatchAddPoints(viewer.profileWindow)
+ }
+ if (window.Potree?.ProfileWindow?.prototype) {
+ patched =
+ tryPatchAddPoints(window.Potree.ProfileWindow.prototype) || patched
+ }
+
+ const afterPatched = () => {
+ attachSelectionInfoOverride(viewer)
+ }
+
+ if (patched) {
+ afterPatched()
+ } else {
+ // Poll until the profile window/prototype is available
+ let tries = 0
+ const maxTries = 40
+ const timer = setInterval(() => {
+ tries += 1
+ if (
+ tryPatchAddPoints(window.Potree?.ProfileWindow?.prototype) ||
+ (viewer?.profileWindow && tryPatchAddPoints(viewer.profileWindow))
+ ) {
+ clearInterval(timer)
+ afterPatched()
+ } else if (tries >= maxTries) {
+ clearInterval(timer)
+ }
+ }, 250)
+ }
+}
+
+// Rewrites the selection properties table inside the profile window
+function attachSelectionInfoOverride(viewer) {
+ const getInfoEl = () => document.getElementById('profileSelectionProperties')
+ let infoEl = getInfoEl()
+ if (!infoEl) {
+ const obs = new MutationObserver(() => {
+ infoEl = getInfoEl()
+ if (infoEl) {
+ obs.disconnect()
+ monitorSelectionInfo(viewer, infoEl)
+ }
+ })
+ obs.observe(document.body, { childList: true, subtree: true })
+ } else {
+ monitorSelectionInfo(viewer, infoEl)
+ }
+}
+
+function monitorSelectionInfo(viewer, infoEl) {
+ let scheduled = false
+ let selfUpdating = false
+
+ const updateOnce = () => {
+ scheduled = false
+ selfUpdating = true
+ try {
+ const table = infoEl.querySelector('table')
+ if (!table) return
+ const rows = [...table.querySelectorAll('tr')]
+ if (rows.length < 3) return
+
+ const pw = viewer?.profileWindow
+ const pos = pw?.viewerPickSphere?.position
+ const sp = pw?.selectedPoint
+ if (!pos || !sp) return
+
+ // Use preserved ECEF Z if we have it; otherwise fall back to pos.z
+ const trueZ = Number(sp?.ecefZWorld ?? NaN)
+ let lon, lat
+ if (Number.isFinite(trueZ)) {
+ ;[lon, lat] = proj4(ecef, wgs84, [pos.x, pos.y, trueZ])
+ } else {
+ ;[lon, lat] = proj4(ecef, wgs84, [pos.x, pos.y, pos.z])
+ }
+ const elevation = pos.z
+
+ const setRow = (row, label, val) => {
+ const tds = row.querySelectorAll('td')
+ if (tds[0] && tds[0].textContent !== label) tds[0].textContent = label
+ if (tds[1]) {
+ const txt = Number.isFinite(val)
+ ? val.toLocaleString(undefined, {
+ minimumFractionDigits: 4,
+ maximumFractionDigits: 4
+ })
+ : ''
+ if (tds[1].textContent !== txt) tds[1].textContent = txt
+ }
+ }
+
+ setRow(rows[0], 'lon', lon)
+ setRow(rows[1], 'lat', lat)
+ setRow(rows[2], 'elevation', elevation)
+
+ // Remove unwanted rows from the hover info table
+ const labelsToHide = new Set([
+ 'intensity',
+ 'return number',
+ 'number of returns',
+ 'classification flags',
+ 'classification',
+ 'user data',
+ 'scan angle',
+ 'gps time',
+ 'gps-time',
+ 'rgba'
+ ])
+
+ // iterate over a snapshot to avoid issues while removing
+ const allRows = [...table.querySelectorAll('tr')]
+ for (let i = 3; i < allRows.length; i++) {
+ const row = allRows[i]
+ const labelCell = row.querySelector('td')
+ const label = (labelCell?.textContent || '').trim().toLowerCase()
+ if (labelsToHide.has(label)) {
+ row.remove()
+ }
+ }
+ } finally {
+ setTimeout(() => {
+ selfUpdating = false
+ }, 0)
+ }
+ }
+
+ const mo = new MutationObserver(() => {
+ if (selfUpdating) return
+ if (!scheduled) {
+ scheduled = true
+ requestAnimationFrame(updateOnce)
+ }
+ })
+ mo.observe(infoEl, { childList: true, subtree: true })
+ requestAnimationFrame(updateOnce)
+}
diff --git a/src/AnnotationControl/annotationPanel.css b/src/AnnotationControl/annotationPanel.css
index 13a3b2e..f250dba 100644
--- a/src/AnnotationControl/annotationPanel.css
+++ b/src/AnnotationControl/annotationPanel.css
@@ -27,8 +27,8 @@ img.button-icon[src$='/annotation.svg'] {
padding: 8px;
border-radius: 4px;
border: 1px solid #404a50;
- background: #2f383d;
- color: #cfd5d8;
+ background: #636262;
+ color: #636262;
font-family: inherit;
font-size: 12px;
line-height: 1.3;
@@ -44,10 +44,6 @@ img.button-icon[src$='/annotation.svg'] {
margin-top: 6px;
}
-.annotation-add-button {
- margin: 10px 0;
-}
-
.annotation-empty {
opacity: 0.6;
padding: 10px;
@@ -350,37 +346,68 @@ img.button-icon[src$='/annotation.svg'] {
flex: 0 0 18px;
}
+.pv-menu-list_annotations-panel {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ overflow: hidden;
+}
+
/* Add button */
.annotation-add-button {
- background: linear-gradient(180deg, #f6f6f6 0%, #e9e9e9 100%);
- color: #222;
- padding: 8px 16px;
- min-width: 140px;
- height: 38px;
display: block;
- margin: 12px auto;
- border-radius: 6px;
+ width: 80%;
+ margin: 20px 0 10px;
+ padding: 10px 10px;
font-size: 13px;
- font-weight: 700;
- box-shadow: 0 1px 0 rgba(255, 255, 255, 0.6) inset;
- border: 1px solid #cfcfcf;
+ font-weight: 500;
+ background-color: #636262;
+ color: #ffffff;
+ border: 1px solid #555;
+ border-radius: 4px;
cursor: pointer;
- text-align: center;
-}
-.annotation-add-button .add-label {
- color: #222;
- font-weight: 700;
+ transition:
+ background-color 0.2s ease,
+ transform 0.1s ease;
}
+
.annotation-add-button:hover {
- background: linear-gradient(180deg, #f3f3f3 0%, #e2e2e2 100%);
- border-color: #bfbfbf;
+ background-color: #8f8f8f;
}
-.annotation-add-button:active {
- transform: translateY(1px);
- background: linear-gradient(180deg, #e9e9e9 0%, #dbdbdb 100%);
- box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.06);
+
+#labelToggleContainer {
+ margin: 8px 0 6px;
+ padding-left: 4px;
}
-.annotation-add-button:focus {
- outline: 2px solid rgba(100, 100, 100, 0.12);
- outline-offset: 2px;
+#labelToggleContainer .labels-legend {
+ font-size: 13px;
+ color: #ddd;
+ margin-bottom: 4px;
+}
+
+.toggle-group {
+ display: flex;
+ width: 265px;
+ border: 1px solid black;
+ border-radius: 4px;
+ overflow: hidden;
+}
+.toggle-group button {
+ flex: 1;
+ padding: 6px 15px;
+ background: #a7a9aa;
+ color: #3d3c3c;
+ border: 0;
+ cursor: pointer;
+ font-weight: 300;
+ transition: background 0.2s;
+}
+
+.toggle-group button:not(:last-child) {
+ border-right: 1px solid #555;
+}
+
+.toggle-group button.active {
+ background: #c7c9ca;
+ color: #000;
}
diff --git a/src/AnnotationControl/annotationPanel.js b/src/AnnotationControl/annotationPanel.js
index cffe313..684094d 100644
--- a/src/AnnotationControl/annotationPanel.js
+++ b/src/AnnotationControl/annotationPanel.js
@@ -31,7 +31,7 @@ export function initAnnotationsPanel(viewer) {
header.appendChild(headerSpan)
const panel = document.createElement('div')
- panel.className = 'pv-menu-list annotations-panel'
+ panel.className = 'pv-menu-list_annotations-panel'
const listContainerDiv = document.createElement('div')
listContainerDiv.id = 'annotations_list'
@@ -68,6 +68,47 @@ export function initAnnotationsPanel(viewer) {
targetContainer = panel.querySelector('#annotations_list')
}
}
+
+ // --- Add Show/Hide labels toggle group ---
+ const panelEl =
+ targetContainer.closest('.pv-menu-list_annotations-panel') ||
+ targetContainer.parentElement
+
+ if (!panelEl.querySelector('#labelToggleContainer')) {
+ const controls = document.createElement('div')
+ controls.id = 'labelToggleContainer'
+ controls.innerHTML = `
+ Show/Hide saved locations
+
+
+
+
+ `
+ // Insert before list of annotations
+ panelEl.insertBefore(controls, targetContainer)
+
+ // show/hide all annotations
+ const setLabelsVisible = (visible) => {
+ const cont = document.getElementById('potree_annotation_container')
+ if (cont) cont.style.display = visible ? '' : 'none'
+ }
+
+ const showBtn = controls.querySelector('#showLabelsBtn')
+ const hideBtn = controls.querySelector('#hideLabelsBtn')
+
+ showBtn.addEventListener('click', () => {
+ setLabelsVisible(true)
+ showBtn.classList.add('active')
+ hideBtn.classList.remove('active')
+ })
+
+ hideBtn.addEventListener('click', () => {
+ setLabelsVisible(false)
+ hideBtn.classList.add('active')
+ showBtn.classList.remove('active')
+ })
+ }
+
if (!targetContainer) {
console.warn(
'Annotations list container not found and dynamic injection failed'
@@ -947,6 +988,18 @@ export function initAnnotationsPanel(viewer) {
annotationHeader.nextElementSibling.style.display = ''
}
}
+ // Ensure annotation labels are shown when starting to add a location.
+ // The "Show" button will be shown as active and reveals Potree's annotation container.
+ try {
+ const showBtn = document.getElementById('showLabelsBtn')
+ const hideBtn = document.getElementById('hideLabelsBtn')
+ if (showBtn) showBtn.classList.add('active')
+ if (hideBtn) hideBtn.classList.remove('active')
+ const cont = document.getElementById('potree_annotation_container')
+ if (cont) cont.style.display = ''
+ } catch (e) {
+ console.warn('Could not enable annotation labels on add', e)
+ }
// Capture current camera view (position) at the moment the user clicks Add
let camPos = null
try {
diff --git a/src/AcceptedFiltering/threePanels.css b/src/Filter/filter.css
similarity index 76%
rename from src/AcceptedFiltering/threePanels.css
rename to src/Filter/filter.css
index 8dbde01..e3a377d 100644
--- a/src/AcceptedFiltering/threePanels.css
+++ b/src/Filter/filter.css
@@ -1,12 +1,10 @@
/* ---------- Buttons (shared look) ---------- */
/* Reuse your accepted button style for all four */
#btnDoElevationControl,
-#doAcceptedFiltering,
-#btnTHU,
-#btnTVU,
-#btnTHUFilter {
+#doAcceptedFiltering {
display: flex;
width: 100%;
+ justify-content: center;
margin: 6px 0 10px;
padding: 10px 10px;
font-size: 13px;
@@ -22,55 +20,34 @@
}
#btnDoElevationControl:hover,
-#doAcceptedFiltering:hover,
-#btnTHU:hover,
-#btnTVU:hover,
-#btnTHUFilter:hover {
+#doAcceptedFiltering:hover {
background-color: #8f8f8f;
}
#btnDoElevationControl:active,
-#doAcceptedFiltering:active,
-#btnTHU:active,
-#btnTVU:active,
-#btnTHUFilter:active {
+#doAcceptedFiltering:active {
transform: scale(0.97);
background-color: #a8a6a6;
}
/* Optional: “active mode” outline if you toggle a class via JS */
#btnDoElevationControl.active,
-#doAcceptedFiltering.active,
-#btnTHU.active,
-#btnTVU.active,
-#btnTHUFilter:active {
+#doAcceptedFiltering.active {
outline: 2px solid #7ba8ff;
outline-offset: 1px;
}
-/* THU/TVU side-by-side */
-#thu_tvu_list .thu-tvu-row {
- display: flex;
- gap: 6px;
-}
-#thu_tvu_list .thu-tvu-row > button {
- flex: 1 1 50%;
- margin: 0;
-}
-
/* ---------- Panels / moved containers ---------- */
/* Keep Potree’s moved subtrees neat and full-width inside our panels */
-#elevation2_list [id='materials.elevation_container'],
-#thu_tvu_list [id='materials.extra_container'] {
+#elevation_list [id='materials.elevation_container'] {
width: 100%;
box-sizing: border-box;
padding: 6px 8px; /* small breathing room since we moved it out of Appearance */
}
/* Slight spacing inside our panel lists (under the button) */
-#elevation2_list,
-#accepted_list_host,
-#thu_tvu_list {
+#elevation_list,
+#accepted_list_host {
display: block;
padding: 4px 0;
}
@@ -101,16 +78,14 @@
/* ---------- Accordions / headers (light touch) ---------- */
/* Don’t fight jQuery-UI’s theme. Just small spacing adjustments. */
-#menu_elevation2 + .pv-menu-list,
-#menu_accepted + .pv-menu-list,
-#menu_thu_tvu + .pv-menu-list {
+#menu_elevation + .pv-menu-list,
+#menu_accepted + .pv-menu-list {
padding-top: 6px;
}
/* Optional: header label color alignment with dark UI */
-#menu_elevation2 span,
-#menu_accepted span,
-#menu_thu_tvu span {
+#menu_elevation span,
+#menu_accepted span {
color: #e6e6e6;
font-weight: 600;
letter-spacing: 0.2px;
@@ -149,3 +124,11 @@
background-color: #000;
box-shadow: 0 0 4px 1px #fff8;
}
+
+#gradient_repeat_option fieldset {
+ margin: 15px 0px 12px 0px !important;
+}
+
+#gradient_repeat_option fieldset legend {
+ margin: 0px 0px 5px 0px !important;
+}
diff --git a/src/AcceptedFiltering/threePanels.js b/src/Filter/filter.js
similarity index 90%
rename from src/AcceptedFiltering/threePanels.js
rename to src/Filter/filter.js
index e39cf65..981b6a8 100644
--- a/src/AcceptedFiltering/threePanels.js
+++ b/src/Filter/filter.js
@@ -1,7 +1,10 @@
-// Three Potree sidebar sections with buttons + panel bodies:
-// • Elevation → moves #materials.elevation_container into our Elevation body
-// • Accepted → custom UI fully defined here (no external module)
-
+/** Two Potree sidebar sections with buttons + panel bodies:
+ *
+ * • Elevation → moves #materials.elevation_container into our Elevation body
+ * Used for controlling the elevation gradient with a slider and altering between different gradient schemes
+ * • Accepted → custom UI fully defined here (no external module)
+ * Used for indicating which points/surveys are accepted as the seabed and which are not
+ */
const byId = (id) => document.getElementById(id)
/**
@@ -88,7 +91,7 @@ function ensurePanelScaffold(listId) {
* @param {'elevation'|'accepted'} key
*/
function showOnly(key) {
- const elevBody = byId('elevation2_list')?.querySelector('.panel-body')
+ const elevBody = byId('elevation_list')?.querySelector('.panel-body')
const accBody = byId('accepted_list_host')?.querySelector('.panel-body')
if (elevBody) elevBody.style.display = key === 'elevation' ? '' : 'none'
@@ -150,7 +153,7 @@ function selectCloudNode(hooks) {
* @param {number} pollEvery
* @returns {Promise}
*/
-async function waitForOrPoll(id, softMs = 1400, pollEvery = 120) {
+async function waitForOrPoll(id, softMs = 1400, pollEvery = 10) {
const start = performance.now()
while (performance.now() - start < softMs) {
const el = byId(id)
@@ -168,9 +171,9 @@ function createElevationPanel() {
insertSection({
headerId: 'menu_elevation',
headerText: 'Elevation Control',
- listId: 'elevation2_list'
+ listId: 'elevation_list'
})
- ensurePanelScaffold('elevation2_list')
+ ensurePanelScaffold('elevation_list')
}
/**
@@ -178,7 +181,7 @@ function createElevationPanel() {
* @param {{onActivateElevation?:Function}} hooks
*/
function ensureElevationButton(hooks) {
- const { btns } = ensurePanelScaffold('elevation2_list')
+ const { btns } = ensurePanelScaffold('elevation_list')
if (!btns || byId('btnDoElevationControl')) return
const btn = document.createElement('button')
@@ -186,6 +189,10 @@ function ensureElevationButton(hooks) {
btn.type = 'button'
btn.textContent = 'Activate elevation control'
btn.addEventListener('click', () => {
+ const elevBtn = byId('btnDoElevationControl')
+ const accBtn = byId('doAcceptedFiltering')
+ if (elevBtn) elevBtn.style.display = 'none'
+ if (accBtn) accBtn.style.display = ''
switchMode('elevation', hooks?.onActivateElevation, hooks)
})
btns.appendChild(btn)
@@ -219,7 +226,7 @@ function setUpElevationSlider(hooks) {
* @returns {boolean} true if moved or already in place
*/
function moveElevationContainer(hooks) {
- const { body } = ensurePanelScaffold('elevation2_list')
+ const { body } = ensurePanelScaffold('elevation_list')
const src = byId('materials.elevation_container')
if (!body || !src) return false
@@ -228,6 +235,7 @@ function moveElevationContainer(hooks) {
setUpElevationSlider(hooks)
accordionRefresh()
}
+ src.style.removeProperty('display')
return true
}
@@ -279,6 +287,10 @@ function ensureAcceptedButton(hooks) {
btn.type = 'button'
btn.textContent = 'Activate accepted filter'
btn.addEventListener('click', () => {
+ const accBtn = byId('doAcceptedFiltering')
+ const elevBtn = byId('btnDoElevationControl')
+ if (accBtn) accBtn.style.display = 'none'
+ if (elevBtn) elevBtn.style.display = ''
switchMode('accepted', hooks?.onActivateAccepted, hooks)
})
btns.appendChild(btn)
@@ -412,7 +424,7 @@ function attachSelfHealing(activeGetter, hooks) {
const mode = activeGetter()
if (mode === 'elevation') {
const src = byId('materials.elevation_container')
- const { body } = ensurePanelScaffold('elevation2_list')
+ const { body } = ensurePanelScaffold('elevation_list')
if (src && body && src.parentNode !== body) moveElevationContainer(hooks)
}
})
@@ -426,7 +438,7 @@ function attachSelfHealing(activeGetter, hooks) {
* @param {object} viewer Potree viewer (not used directly here but available to hooks)
* @param {{onActivateElevation?:Function, onActivateAccepted?:Function, selectCloudOnce?:Function, onElevationRangeChange?:Function}} hooks
*/
-export function initThreePanels(viewer, hooks = {}) {
+export function initFilterPanels(viewer, hooks = {}) {
// Build sections
initElevationControls(hooks)
initAcceptedControlsInline(hooks)
diff --git a/src/MeasurementControl/measurementsPanel.css b/src/MeasurementControl/measurementsPanel.css
index e9c1aa8..cf5405e 100644
--- a/src/MeasurementControl/measurementsPanel.css
+++ b/src/MeasurementControl/measurementsPanel.css
@@ -284,6 +284,7 @@
margin-top: 10px;
cursor: pointer;
border-radius: 4px;
+ padding: 4px;
}
.tool-with-label:hover {
box-shadow: 0 0 5px #fff8;
diff --git a/src/MeasurementControl/measurementsPanel.js b/src/MeasurementControl/measurementsPanel.js
index aef16fd..00f3517 100644
--- a/src/MeasurementControl/measurementsPanel.js
+++ b/src/MeasurementControl/measurementsPanel.js
@@ -1188,7 +1188,7 @@ export function initMeasurementsPanel(viewer) {
// After tools are moved into `toolsHost`
const toolDescriptions = {
'angle.png': 'Measure angle',
- 'point.svg': 'Add point',
+ 'point.svg': 'Inspect point',
'distance.svg': 'Measure distance',
'height.svg': 'Measure height',
'circle.svg': 'Circle',
diff --git a/src/potreeViewer.js b/src/potreeViewer.js
index 86e1d1b..b78539d 100644
--- a/src/potreeViewer.js
+++ b/src/potreeViewer.js
@@ -1,11 +1,9 @@
import { initAnnotationsPanel } from './AnnotationControl/annotationPanel.js'
import { initMeasurementsPanel } from './MeasurementControl/measurementsPanel.js'
import { initMiniMap } from './MiniMap/miniMap.js'
-import {
- initThreePanels,
- toggleAcceptedLegend
-} from './AcceptedFiltering/threePanels.js'
+import { initFilterPanels, toggleAcceptedLegend } from './Filter/filter.js'
import { ecef } from './config.js'
+import { init2DProfileOverride } from './2DProfileOverride/2DProfileOverride.js'
/**
* Initializes the Potree viewer used to visualize the point cloud.
@@ -58,9 +56,6 @@ export async function createPotreeViewer(
viewer.loadGUI(() => {
viewer.setLanguage('en')
- $('#menu_appearance').next().hide()
- $('#menu_tools').next().hide()
- $('#menu_scene').next().hide()
$('#menu_filters').remove()
viewer.toggleSidebar()
@@ -90,7 +85,7 @@ export async function createPotreeViewer(
})
}
- initThreePanels(viewer, {
+ initFilterPanels(viewer, {
onActivateElevation: () => {
const $ = window.jQuery || window.$
const slider = $ ? $('#sldHeightRange') : null
@@ -125,6 +120,12 @@ export async function createPotreeViewer(
makeGlobeBackgroundOption()
+ // Show compass
+ viewer.compass.setVisible(true)
+
+ // Apply runtime overrides for the 2D Profile tool
+ init2DProfileOverride(viewer)
+
initMeasurementsPanel(viewer)
initAnnotationsPanel(viewer)
initMiniMap(viewer)
@@ -133,7 +134,7 @@ export async function createPotreeViewer(
// Initialize camera position and target point (manually chosen)
viewer.scene.view.setView(
[4094989.813, 59057.337, 8363694.681], // Initial camera position
- [1500922.651, 510673.03, 5427934.722] // Initial target point
+ [1821061.22, 266704.21, 6084038.77] // Initial target point
)
return viewer
@@ -205,6 +206,9 @@ function overrideShaderForGradient(pc) {
}
}
+// Prevent overlapping scroll freezing when activating filters quickly
+let suppressionActive = false
+
/**
* Freeze all scrollable ancestors of a given root during an action (e.g., jsTree select)
* Need this so that when Elevation control or Accepted filter is activated the sidebar doesn't scroll down to the Scene panel
@@ -212,6 +216,13 @@ function overrideShaderForGradient(pc) {
* @param {*} action
*/
function suppressSidebarAutoScroll(action, holdMs = 350) {
+ // --- Re-entrancy guard ---
+ if (suppressionActive) {
+ action()
+ return
+ }
+ suppressionActive = true
+
// anchor on the tree root; fall back to the menu if not found
const treeRoot =
document.querySelector('#scene_objects') ||
@@ -228,8 +239,10 @@ function suppressSidebarAutoScroll(action, holdMs = 350) {
if (canScroll) scrollers.push(el)
el = el.parentElement
}
+
if (!scrollers.length) {
action()
+ suppressionActive = false
return
}
@@ -261,20 +274,19 @@ function suppressSidebarAutoScroll(action, holdMs = 350) {
const origFocus = HTMLElement.prototype.focus
HTMLElement.prototype.focus = function (opts) {
- // force preventScroll behavior even if caller didn't ask
try {
origFocus.call(this, { ...(opts || {}), preventScroll: true })
} catch {
origFocus.call(this)
}
}
+
try {
action()
} finally {
const until = performance.now() + holdMs
const restoreLoop = () => {
- // keep snapping until the selection animations/handlers settle
states.forEach(({ el, top, left }) => {
el.scrollTop = top
el.scrollLeft = left
@@ -297,8 +309,11 @@ function suppressSidebarAutoScroll(action, holdMs = 350) {
if (h) el.removeEventListener('scroll', h)
el.style.overflow = overflow
})
+ // --- Release the guard ---
+ suppressionActive = false
}
}
+
requestAnimationFrame(restoreLoop)
}
}