From d2b1b5857160165a006dfbf547c305592a83edde Mon Sep 17 00:00:00 2001 From: AdrianSolberg Date: Wed, 29 Oct 2025 10:38:00 +0100 Subject: [PATCH 01/16] feat(#15): show compass by default --- src/potreeViewer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index ad7a0dc..8041580 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -125,6 +125,9 @@ export async function createPotreeViewer( makeGlobeBackgroundOption() + // Show compass + viewer.compass.setVisible(true) + initMeasurementsPanel(viewer) initAnnotationsPanel(viewer) initMiniMap(viewer) From 9e1a69c06cc3ceac8799053a851c10124e907fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20Wahlstr=C3=B8m?= Date: Wed, 29 Oct 2025 13:35:20 +0100 Subject: [PATCH 02/16] feat(#48): :sparkles: Added hide/show button for saved positions --- src/AcceptedFiltering/threePanels.css | 1 + src/AnnotationControl/annotationPanel.css | 81 +++++++++++++-------- src/AnnotationControl/annotationPanel.js | 44 ++++++++++- src/MeasurementControl/measurementsPanel.js | 2 +- 4 files changed, 97 insertions(+), 31 deletions(-) diff --git a/src/AcceptedFiltering/threePanels.css b/src/AcceptedFiltering/threePanels.css index 8dbde01..4acff68 100644 --- a/src/AcceptedFiltering/threePanels.css +++ b/src/AcceptedFiltering/threePanels.css @@ -7,6 +7,7 @@ #btnTHUFilter { display: flex; width: 100%; + justify-content: center; margin: 6px 0 10px; padding: 10px 10px; font-size: 13px; diff --git a/src/AnnotationControl/annotationPanel.css b/src/AnnotationControl/annotationPanel.css index 13a3b2e..484fb8a 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,64 @@ 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; + justify-content: center; + 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; - cursor: pointer; - text-align: center; -} -.annotation-add-button .add-label { - color: #222; - font-weight: 700; + font-weight: 500; + background-color: #636262; + color: #ffffff; + border: 1px solid #555; + border-radius: 4px; + cursor: pointer; + 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 { +/* .annotation-add-button:active { transform: translateY(1px); - background: linear-gradient(180deg, #e9e9e9 0%, #dbdbdb 100%); + background: linear-gradient(180deg, #8f8f8f 0%, #8f8f8f 100%); box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.06); +} */ +/* .annotation-add-button:focus { + background-color: #8f8f8f; +} */ + +#labelToggleContainer { margin: 8px 0 6px; padding-left: 4px; } +#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; } -.annotation-add-button:focus { - outline: 2px solid rgba(100, 100, 100, 0.12); - outline-offset: 2px; +.toggle-group button { + flex: 1; + padding: 6px 15px; + background: #A7A9AA; + color: #3d3c3c; + border: 0; + cursor: pointer; + font-weight: 300; + transition: background .2s; } +.toggle-group button:not(:last-child) { border-right: 1px solid #555; } +.toggle-group button.active { background: #C7C9CA; color: #000; } \ No newline at end of file diff --git a/src/AnnotationControl/annotationPanel.js b/src/AnnotationControl/annotationPanel.js index cffe313..7087f7d 100644 --- a/src/AnnotationControl/annotationPanel.js +++ b/src/AnnotationControl/annotationPanel.js @@ -31,13 +31,14 @@ 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' listContainerDiv.className = 'auto' panel.appendChild(listContainerDiv) + // Insert after measurement panel but before tools, or at end if not found const measurements = document.querySelector('.measurements-panel') if (measurements) { @@ -68,6 +69,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' diff --git a/src/MeasurementControl/measurementsPanel.js b/src/MeasurementControl/measurementsPanel.js index 9051705..d4e9454 100644 --- a/src/MeasurementControl/measurementsPanel.js +++ b/src/MeasurementControl/measurementsPanel.js @@ -653,7 +653,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', From fc1c376ced7ffc06b80ec72b4f7762f5c55427b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20Wahlstr=C3=B8m?= Date: Wed, 29 Oct 2025 13:39:45 +0100 Subject: [PATCH 03/16] style: :art: Ran prettier format --- src/AnnotationControl/annotationPanel.css | 29 +++++++--- src/AnnotationControl/annotationPanel.js | 59 ++++++++++---------- src/MeasurementControl/measurementsPanel.css | 8 +-- src/MeasurementControl/measurementsPanel.js | 37 ++++++------ 4 files changed, 70 insertions(+), 63 deletions(-) diff --git a/src/AnnotationControl/annotationPanel.css b/src/AnnotationControl/annotationPanel.css index 484fb8a..d564f91 100644 --- a/src/AnnotationControl/annotationPanel.css +++ b/src/AnnotationControl/annotationPanel.css @@ -346,8 +346,7 @@ img.button-icon[src$='/annotation.svg'] { flex: 0 0 18px; } - -.pv-menu-list_annotations-panel{ +.pv-menu-list_annotations-panel { display: flex; flex-direction: column; align-items: center; @@ -367,7 +366,7 @@ img.button-icon[src$='/annotation.svg'] { color: #ffffff; border: 1px solid #555; border-radius: 4px; - cursor: pointer; + cursor: pointer; transition: background-color 0.2s ease, transform 0.1s ease; @@ -385,8 +384,15 @@ img.button-icon[src$='/annotation.svg'] { background-color: #8f8f8f; } */ -#labelToggleContainer { margin: 8px 0 6px; padding-left: 4px; } -#labelToggleContainer .labels-legend { font-size: 13px; color: #ddd; margin-bottom: 4px; } +#labelToggleContainer { + margin: 8px 0 6px; + padding-left: 4px; +} +#labelToggleContainer .labels-legend { + font-size: 13px; + color: #ddd; + margin-bottom: 4px; +} .toggle-group { display: flex; @@ -398,12 +404,17 @@ img.button-icon[src$='/annotation.svg'] { .toggle-group button { flex: 1; padding: 6px 15px; - background: #A7A9AA; + background: #a7a9aa; color: #3d3c3c; border: 0; cursor: pointer; font-weight: 300; - transition: background .2s; + transition: background 0.2s; +} +.toggle-group button:not(:last-child) { + border-right: 1px solid #555; +} +.toggle-group button.active { + background: #c7c9ca; + color: #000; } -.toggle-group button:not(:last-child) { border-right: 1px solid #555; } -.toggle-group button.active { background: #C7C9CA; color: #000; } \ No newline at end of file diff --git a/src/AnnotationControl/annotationPanel.js b/src/AnnotationControl/annotationPanel.js index 7087f7d..170bd58 100644 --- a/src/AnnotationControl/annotationPanel.js +++ b/src/AnnotationControl/annotationPanel.js @@ -38,7 +38,6 @@ export function initAnnotationsPanel(viewer) { listContainerDiv.className = 'auto' panel.appendChild(listContainerDiv) - // Insert after measurement panel but before tools, or at end if not found const measurements = document.querySelector('.measurements-panel') if (measurements) { @@ -72,42 +71,42 @@ export function initAnnotationsPanel(viewer) { // --- Add Show/Hide labels toggle group --- const panelEl = - targetContainer.closest('.pv-menu-list_annotations-panel') || - targetContainer.parentElement; + targetContainer.closest('.pv-menu-list_annotations-panel') || + targetContainer.parentElement if (!panelEl.querySelector('#labelToggleContainer')) { - const controls = document.createElement('div'); - controls.id = 'labelToggleContainer'; - controls.innerHTML = ` + 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'); - }); + ` + // 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) { diff --git a/src/MeasurementControl/measurementsPanel.css b/src/MeasurementControl/measurementsPanel.css index f22b094..b931d82 100644 --- a/src/MeasurementControl/measurementsPanel.css +++ b/src/MeasurementControl/measurementsPanel.css @@ -277,7 +277,7 @@ flex-direction: row; align-items: center; margin-top: 10px; - cursor: pointer; + cursor: pointer; border-radius: 4px; } .tool-with-label:hover { @@ -285,7 +285,7 @@ } .tool-with-label:hover img { - filter: brightness(1.7); + filter: brightness(1.7); } .tool-with-label:hover .tool-label { @@ -297,7 +297,5 @@ margin-top: 2px; margin-left: 4px; color: #aaa; - pointer-events: none; + pointer-events: none; } - - diff --git a/src/MeasurementControl/measurementsPanel.js b/src/MeasurementControl/measurementsPanel.js index d4e9454..3021108 100644 --- a/src/MeasurementControl/measurementsPanel.js +++ b/src/MeasurementControl/measurementsPanel.js @@ -663,32 +663,31 @@ export function initMeasurementsPanel(viewer) { 'sphere_distances.svg': 'Sphere volume', 'profile.svg': '2D height profile', 'reset_tools.svg': 'Remove all' - }; + } - const toolIcons = existingTools.querySelectorAll('img'); - toolIcons.forEach(img => { - const src = img.getAttribute('src'); - const file = src.split('/').pop(); // extract icon name - const baseName = file.replace(/\.[^/.]+$/, ''); + const toolIcons = existingTools.querySelectorAll('img') + toolIcons.forEach((img) => { + const src = img.getAttribute('src') + const file = src.split('/').pop() // extract icon name + const baseName = file.replace(/\.[^/.]+$/, '') if (toolDescriptions[file]) { - const wrapper = document.createElement('div'); - wrapper.className = 'tool-with-label'; - wrapper.id = `tool-wrapper-${baseName}`; + const wrapper = document.createElement('div') + wrapper.className = 'tool-with-label' + wrapper.id = `tool-wrapper-${baseName}` - wrapper.addEventListener('click', () => img.click()); + wrapper.addEventListener('click', () => img.click()) - img.parentNode.insertBefore(wrapper, img); - wrapper.appendChild(img); + img.parentNode.insertBefore(wrapper, img) + wrapper.appendChild(img) - const label = document.createElement('span'); - label.className = 'tool-label'; - label.textContent = toolDescriptions[file]; - label.id = `label-${file.replace(/\.[^/.]+$/, '')}`; - wrapper.appendChild(label); + const label = document.createElement('span') + label.className = 'tool-label' + label.textContent = toolDescriptions[file] + label.id = `label-${file.replace(/\.[^/.]+$/, '')}` + wrapper.appendChild(label) } - }); - + }) // Move measurement options UI into our tools host if (toolsHost) { From 1ead61bbe4ffd16a281588d0ea260eaf8749a2c7 Mon Sep 17 00:00:00 2001 From: Kleinc Date: Wed, 29 Oct 2025 14:03:57 +0100 Subject: [PATCH 04/16] style(#48): :lipstick: Fix spacing in elevation control --- src/AcceptedFiltering/threePanels.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/AcceptedFiltering/threePanels.css b/src/AcceptedFiltering/threePanels.css index 4acff68..95b812e 100644 --- a/src/AcceptedFiltering/threePanels.css +++ b/src/AcceptedFiltering/threePanels.css @@ -150,3 +150,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; +} From b4760062921e2e0b12ae6de41729b9ad3e250ced Mon Sep 17 00:00:00 2001 From: Kleinc Date: Wed, 29 Oct 2025 14:37:18 +0100 Subject: [PATCH 05/16] refactor(#53): :recycle: Change inital target position --- src/potreeViewer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index 8041580..e710bd3 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -136,7 +136,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 From be4bf5f81a30dbfacebe45a99bf96f865004f71c Mon Sep 17 00:00:00 2001 From: Kleinc Date: Wed, 29 Oct 2025 14:45:54 +0100 Subject: [PATCH 06/16] style(#48): :lipstick: Remove unecessary lines and code commented out --- src/AnnotationControl/annotationPanel.css | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/AnnotationControl/annotationPanel.css b/src/AnnotationControl/annotationPanel.css index d564f91..f250dba 100644 --- a/src/AnnotationControl/annotationPanel.css +++ b/src/AnnotationControl/annotationPanel.css @@ -356,7 +356,6 @@ img.button-icon[src$='/annotation.svg'] { /* Add button */ .annotation-add-button { display: block; - justify-content: center; width: 80%; margin: 20px 0 10px; padding: 10px 10px; @@ -375,14 +374,6 @@ img.button-icon[src$='/annotation.svg'] { .annotation-add-button:hover { background-color: #8f8f8f; } -/* .annotation-add-button:active { - transform: translateY(1px); - background: linear-gradient(180deg, #8f8f8f 0%, #8f8f8f 100%); - box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.06); -} */ -/* .annotation-add-button:focus { - background-color: #8f8f8f; -} */ #labelToggleContainer { margin: 8px 0 6px; @@ -411,9 +402,11 @@ img.button-icon[src$='/annotation.svg'] { 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; From a3c4875f465193e6807898900d033dd67aa87c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20Wahlstr=C3=B8m?= Date: Thu, 30 Oct 2025 11:09:15 +0100 Subject: [PATCH 07/16] fix(#46): :bug: Fixed display bug (I think) --- src/AcceptedFiltering/threePanels.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/AcceptedFiltering/threePanels.js b/src/AcceptedFiltering/threePanels.js index 249df0f..0b751ee 100644 --- a/src/AcceptedFiltering/threePanels.js +++ b/src/AcceptedFiltering/threePanels.js @@ -88,7 +88,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 +150,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 +168,9 @@ function createElevationPanel() { insertSection({ headerId: 'menu_elevation', headerText: 'Elevation Control', - listId: 'elevation2_list' + listId: 'elevation_list' }) - ensurePanelScaffold('elevation2_list') + ensurePanelScaffold('elevation_list') } /** @@ -178,7 +178,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') @@ -219,7 +219,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 +228,7 @@ function moveElevationContainer(hooks) { setUpElevationSlider(hooks) accordionRefresh() } + src.style.removeProperty('display') return true } @@ -412,7 +413,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) } }) From a016ed7ae0c564fb1a19bafaeb99bd030eb4452d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20Wahlstr=C3=B8m?= Date: Thu, 30 Oct 2025 12:45:43 +0100 Subject: [PATCH 08/16] refactor(#48): :truck: Updated elevation and acepted code to be more descriptic --- index.html | 2 +- .../threePanels.css => Filter/filter.css} | 48 +++++-------------- .../threePanels.js => Filter/filter.js} | 13 +++-- src/potreeViewer.js | 4 +- 4 files changed, 22 insertions(+), 45 deletions(-) rename src/{AcceptedFiltering/threePanels.css => Filter/filter.css} (75%) rename src/{AcceptedFiltering/threePanels.js => Filter/filter.js} (96%) diff --git a/index.html b/index.html index 2f98510..2c4e868 100644 --- a/index.html +++ b/index.html @@ -39,7 +39,7 @@ href="/src/MeasurementControl/measurementsPanel.css" /> - + diff --git a/src/AcceptedFiltering/threePanels.css b/src/Filter/filter.css similarity index 75% rename from src/AcceptedFiltering/threePanels.css rename to src/Filter/filter.css index e83b9e6..8f7b24c 100644 --- a/src/AcceptedFiltering/threePanels.css +++ b/src/Filter/filter.css @@ -1,10 +1,7 @@ /* ---------- Buttons (shared look) ---------- */ /* Reuse your accepted button style for all four */ #btnDoElevationControl, -#doAcceptedFiltering, -#btnTHU, -#btnTVU, -#btnTHUFilter { +#doAcceptedFiltering { display: flex; width: 100%; margin: 6px 0 10px; @@ -22,55 +19,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 +77,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; diff --git a/src/AcceptedFiltering/threePanels.js b/src/Filter/filter.js similarity index 96% rename from src/AcceptedFiltering/threePanels.js rename to src/Filter/filter.js index 0b751ee..3bbfd08 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) /** @@ -427,7 +430,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/potreeViewer.js b/src/potreeViewer.js index 8041580..bfe1736 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -4,7 +4,7 @@ import { initMiniMap } from './MiniMap/miniMap.js' import { initThreePanels, toggleAcceptedLegend -} from './AcceptedFiltering/threePanels.js' +} from './Filter/filter.js' import { ecef } from './config.js' /** @@ -90,7 +90,7 @@ export async function createPotreeViewer( }) } - initThreePanels(viewer, { + initFilterPanels(viewer, { onActivateElevation: () => { const $ = window.jQuery || window.$ const slider = $ ? $('#sldHeightRange') : null From 02f7d20e6cb399d8b49c122eb42eddb8cfc86b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20Wahlstr=C3=B8m?= Date: Thu, 30 Oct 2025 12:47:45 +0100 Subject: [PATCH 09/16] refactor: :adhesive_bandage: One file name changes --- src/potreeViewer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index bfe1736..876c007 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -2,7 +2,7 @@ import { initAnnotationsPanel } from './AnnotationControl/annotationPanel.js' import { initMeasurementsPanel } from './MeasurementControl/measurementsPanel.js' import { initMiniMap } from './MiniMap/miniMap.js' import { - initThreePanels, + initFilterPanels, toggleAcceptedLegend } from './Filter/filter.js' import { ecef } from './config.js' From dbc178233a895b3c0dce2e8f4d57b4ec4a534157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20Wahlstr=C3=B8m?= Date: Thu, 30 Oct 2025 12:51:20 +0100 Subject: [PATCH 10/16] style: :art: Ran prettier --- src/potreeViewer.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index 876c007..d14378e 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -1,10 +1,7 @@ import { initAnnotationsPanel } from './AnnotationControl/annotationPanel.js' import { initMeasurementsPanel } from './MeasurementControl/measurementsPanel.js' import { initMiniMap } from './MiniMap/miniMap.js' -import { - initFilterPanels, - toggleAcceptedLegend -} from './Filter/filter.js' +import { initFilterPanels, toggleAcceptedLegend } from './Filter/filter.js' import { ecef } from './config.js' /** @@ -85,7 +82,7 @@ export async function createPotreeViewer( // Helper function to update all point clouds for Accepted filtering function updateAllCloudsAccepted(gradientName) { pointclouds.forEach((pc) => { - pc.material.activeAttributeName = 'Accepted' + pc.material.activeAttributeName = 'accepted' pc.material.gradient = Potree.Gradients[gradientName] }) } From 3d90cb340cdb46ac5f094d31fd7944a49bcec326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20Wahlstr=C3=B8m?= Date: Thu, 30 Oct 2025 12:52:01 +0100 Subject: [PATCH 11/16] style(#46): :art: Ran prettier --- src/Filter/filter.css | 8 ++++---- src/Filter/filter.js | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Filter/filter.css b/src/Filter/filter.css index 8f7b24c..be09a06 100644 --- a/src/Filter/filter.css +++ b/src/Filter/filter.css @@ -31,14 +31,14 @@ /* Optional: “active mode” outline if you toggle a class via JS */ #btnDoElevationControl.active, -#doAcceptedFiltering.active{ +#doAcceptedFiltering.active { outline: 2px solid #7ba8ff; outline-offset: 1px; } /* ---------- Panels / moved containers ---------- */ /* Keep Potree’s moved subtrees neat and full-width inside our panels */ -#elevation_list [id='materials.elevation_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 */ @@ -46,7 +46,7 @@ /* Slight spacing inside our panel lists (under the button) */ #elevation_list, -#accepted_list_host{ +#accepted_list_host { display: block; padding: 4px 0; } @@ -78,7 +78,7 @@ /* ---------- Accordions / headers (light touch) ---------- */ /* Don’t fight jQuery-UI’s theme. Just small spacing adjustments. */ #menu_elevation + .pv-menu-list, -#menu_accepted + .pv-menu-list{ +#menu_accepted + .pv-menu-list { padding-top: 6px; } diff --git a/src/Filter/filter.js b/src/Filter/filter.js index 3bbfd08..40c5d56 100644 --- a/src/Filter/filter.js +++ b/src/Filter/filter.js @@ -1,10 +1,10 @@ /** 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 + * 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) /** From 385185cd06a1ec07b4b97f8aa8cc403937b0623b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20Wahlstr=C3=B8m?= Date: Thu, 30 Oct 2025 14:25:27 +0100 Subject: [PATCH 12/16] Update src/MeasurementControl/measurementsPanel.css Add more padding Co-authored-by: Adrian Haabpiht Solberg --- src/MeasurementControl/measurementsPanel.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MeasurementControl/measurementsPanel.css b/src/MeasurementControl/measurementsPanel.css index b931d82..a848631 100644 --- a/src/MeasurementControl/measurementsPanel.css +++ b/src/MeasurementControl/measurementsPanel.css @@ -279,6 +279,7 @@ margin-top: 10px; cursor: pointer; border-radius: 4px; + padding: 4px; } .tool-with-label:hover { box-shadow: 0 0 5px #fff8; From 1703cb76f5e6c4b42a1ba20e5eb6364b0cfa9dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marie=20Wahlstr=C3=B8m?= Date: Thu, 30 Oct 2025 15:42:14 +0100 Subject: [PATCH 13/16] Update src/potreeViewer.js Fix typo in comment Co-authored-by: Adrian Haabpiht Solberg --- src/potreeViewer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index c86df2f..2e2dd5e 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -205,7 +205,7 @@ function overrideShaderForGradient(pc) { } } -// Prevent overlapping scroll freezes when activating filters quickly +// Prevent overlapping scroll freezing when activating filters quickly let suppressionActive = false /** From 73059dec1e990f47b7bcca065896be2df875b0f1 Mon Sep 17 00:00:00 2001 From: Kleinc Date: Fri, 31 Oct 2025 14:21:04 +0100 Subject: [PATCH 14/16] refactor(#48): :recycle: Remove unecessary code, make annotations always visble when pressing "add a location" --- src/AnnotationControl/annotationPanel.js | 12 ++++++++++++ src/potreeViewer.js | 3 --- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/AnnotationControl/annotationPanel.js b/src/AnnotationControl/annotationPanel.js index 170bd58..684094d 100644 --- a/src/AnnotationControl/annotationPanel.js +++ b/src/AnnotationControl/annotationPanel.js @@ -988,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/potreeViewer.js b/src/potreeViewer.js index 0b13435..5479902 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -55,9 +55,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() From 1d5c5dedc1ed5c87ead242b1697f7fcdc889374d Mon Sep 17 00:00:00 2001 From: Kleinc Date: Sun, 2 Nov 2025 10:09:25 +0100 Subject: [PATCH 15/16] refactor(#48): :recycle: Make elevation control button and accepted filter button more intuitive --- src/Filter/filter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Filter/filter.js b/src/Filter/filter.js index cdea1f8..981b6a8 100644 --- a/src/Filter/filter.js +++ b/src/Filter/filter.js @@ -189,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) @@ -283,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) From 4e0b45e83fa5979898d3ab61661b3c9dd185afd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gaute=20Fl=C3=A6gstad?= Date: Sun, 2 Nov 2025 11:44:00 +0100 Subject: [PATCH 16/16] fix(#56): :bug: 2D profile measurement shows lat,lon,elevation instead of x,y,z Because of the change of coordinate system the z coordinate no longer represents the elevation of a point. Instead of displaying x,y,z it now displays lat, lon, and elevation which is more useful to the user. Also removed some of the attributes which are 0 and cluttering the space --- index.html | 1 + src/2DProfileOverride/2DProfileOverride.css | 12 ++ src/2DProfileOverride/2DProfileOverride.js | 224 ++++++++++++++++++++ src/potreeViewer.js | 4 + 4 files changed, 241 insertions(+) create mode 100644 src/2DProfileOverride/2DProfileOverride.css create mode 100644 src/2DProfileOverride/2DProfileOverride.js diff --git a/index.html b/index.html index 2c4e868..b0488b6 100644 --- a/index.html +++ b/index.html @@ -40,6 +40,7 @@ /> + 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/potreeViewer.js b/src/potreeViewer.js index ab6c461..5baef9a 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -3,6 +3,7 @@ import { initMeasurementsPanel } from './MeasurementControl/measurementsPanel.js import { initMiniMap } from './MiniMap/miniMap.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. @@ -125,6 +126,9 @@ export async function createPotreeViewer( // Show compass viewer.compass.setVisible(true) + // Apply runtime overrides for the 2D Profile tool + init2DProfileOverride(viewer) + initMeasurementsPanel(viewer) initAnnotationsPanel(viewer) initMiniMap(viewer)