diff --git a/index.html b/index.html
index b9c3d02..f29b3c6 100644
--- a/index.html
+++ b/index.html
@@ -38,6 +38,13 @@
rel="stylesheet"
href="/src/MeasurementControl/measurementsPanel.css"
/>
+
+
diff --git a/src/Accepted/accepted.css b/src/Accepted/accepted.css
new file mode 100644
index 0000000..482defa
--- /dev/null
+++ b/src/Accepted/accepted.css
@@ -0,0 +1,56 @@
+/* Accepted Filter: action button */
+#doAcceptedFilter {
+ display: flex;
+ width: 100%;
+ margin: 6px 0 10px;
+ padding: 10px 10px;
+ font-size: 13px;
+ font-weight: 500;
+ background-color: #3a3a3a;
+ color: #ffffff;
+ border: 1px solid #555;
+ border-radius: 4px;
+ cursor: pointer;
+ transition:
+ background-color 0.2s ease,
+ transform 0.1s ease;
+}
+#doAcceptedFilter:hover {
+ background-color: #505050;
+}
+#doAcceptedFilter:active {
+ transform: scale(0.97);
+ background-color: #606060;
+}
+
+/* Legend container */
+#accepted_legend {
+ margin-top: 10px;
+ padding-left: 4px;
+ }
+
+ /* Legend rows */
+ #accepted_legend .legend-row {
+ display: flex;
+ align-items: center;
+ margin: 3px 0;
+ font-size: 13px;
+ color: #ddd; /* visible text */
+ }
+
+ /* Color boxes */
+ #accepted_legend .legend-color {
+ width: 16px;
+ height: 16px;
+ border: 1px solid #777;
+ margin-right: 8px;
+ border-radius: 2px;
+ }
+
+ #accepted_legend .legend-color.accepted {
+ background-color: #fff;
+ }
+
+ #accepted_legend .legend-color.not-accepted {
+ background-color: #000;
+ }
\ No newline at end of file
diff --git a/src/Accepted/accepted.js b/src/Accepted/accepted.js
new file mode 100644
index 0000000..0470f54
--- /dev/null
+++ b/src/Accepted/accepted.js
@@ -0,0 +1,179 @@
+function createAcceptedPanel() {
+ const container = document.getElementById('accepted_list')
+ if (container) return
+
+ const menu = document.getElementById('potree_menu')
+ if (!menu) return
+
+ const header = document.createElement('h3')
+ header.id = 'menu_accepted'
+ header.innerHTML = 'Accepted Filter'
+
+ const panel = document.createElement('div')
+ panel.className = 'pv-menu-list'
+ panel.innerHTML = ''
+
+ // Place above Appearance if possible (same as Elevation)
+ const appearance = document.getElementById('menu_appearance')
+ if (appearance) {
+ menu.insertBefore(panel, appearance)
+ menu.insertBefore(header, panel)
+ } else {
+ menu.appendChild(header)
+ menu.appendChild(panel)
+ }
+
+ if (window.$ && window.$(menu).accordion) {
+ try {
+ window.$(menu).accordion('refresh')
+ } catch (e) {}
+ }
+
+ // Simple toggle if accordion doesn’t manage it
+ header.addEventListener('click', () => {
+ panel.style.display = panel.style.display === 'none' ? '' : 'none'
+ })
+}
+
+function ensureActivationButton(hooks) {
+ const list = document.getElementById('accepted_list')
+ if (!list) return
+ if (list.querySelector('#doAcceptedFilter')) return
+
+ const btn = document.createElement('button')
+ btn.id = 'doAcceptedFilter'
+ btn.type = 'button'
+ btn.textContent = 'Activate accepted filter'
+ btn.addEventListener('click', () => {
+ if (hooks && typeof hooks.onActivateAccepted === 'function') {
+ hooks.onActivateAccepted();
+ }
+ const legend = document.getElementById('accepted_legend');
+ if (legend) legend.style.display = 'block'; // ensure legend shows
+ });
+
+ list.insertBefore(btn, list.firstChild)
+}
+
+function ensureAcceptedLegend() {
+ const list = document.getElementById('accepted_list')
+ if (!list) return null
+
+ let legend = list.querySelector('#accepted_legend')
+ if (!legend) {
+ legend = document.createElement('div')
+ legend.id = 'accepted_legend'
+ legend.style.display = 'none' // hidden until button click
+ legend.innerHTML = `
+
+
+
+
Not accepted points
+
+ `
+ list.appendChild(legend)
+ }
+ return legend
+ }
+
+ // Show/hide legend (called from viewer hook)
+ export function toggleAcceptedLegend(show) {
+ const legend = document.getElementById('accepted_legend')
+ if (legend) legend.style.display = show ? 'block' : 'none'
+ }
+
+// Ensure our list wrapper exists inside the Accepted panel
+function ensureAcceptedListUL() {
+ const host = document.getElementById('accepted_list')
+ if (!host) return null
+
+ let ul = host.querySelector('#accepted_ui')
+ if (!ul) {
+ ul = document.createElement('ul')
+ ul.id = 'accepted_ui'
+ ul.className = 'pv-menu-list'
+ host.appendChild(ul)
+ }
+ return ul
+}
+
+// Move ALL current children from Potree's extra_container into our UL
+function moveAcceptedChildrenOnce() {
+ const source = document.querySelector('#materials\\.extra_container')
+ const targetUL = ensureAcceptedListUL()
+ if (!source || !targetUL) return false
+
+ // Move only the elements that should be visible in a pv-menu-list (divider + li)
+ const nodes = [...source.children].filter(
+ (n) => n.classList.contains('divider') || n.tagName.toLowerCase() === 'li'
+ )
+
+ if (nodes.length === 0) return false
+
+ for (const n of nodes) {
+ targetUL.appendChild(n) // moving preserves event listeners
+ }
+ return true
+}
+
+// Observe Potree's extra_container for FUTURE children; move them as they arrive
+function observeAndMirrorExtraContainer() {
+ const source = document.querySelector('#materials\\.extra_container')
+ if (!source) return null
+
+ const targetUL = ensureAcceptedListUL()
+ if (!targetUL) return null
+
+ const childObserver = new MutationObserver((mutations) => {
+ for (const m of mutations) {
+ if (m.type !== 'childList')
+ continue
+ // On added nodes, move any or .divider into our list
+ ;[...m.addedNodes].forEach((node) => {
+ if (!(node instanceof HTMLElement)) return
+ const isLi = node.tagName && node.tagName.toLowerCase() === 'li'
+ const isDivider = node.classList && node.classList.contains('divider')
+ if (isLi || isDivider) {
+ targetUL.appendChild(node)
+ }
+ })
+ }
+ })
+
+ // Only watch direct children; Potree appends li/divider at this level
+ childObserver.observe(source, { childList: true })
+ return childObserver
+}
+
+export function initAcceptedControls(viewer, hooks = {}) {
+ // 1) Always render panel + button
+ createAcceptedPanel()
+ ensureActivationButton(hooks)
+ ensureAcceptedListUL()
+ ensureAcceptedLegend()
+
+ // 2) Wait for Potree to create extra_container, then move children & keep mirroring
+// const menu =
+// document.getElementById('potree_menu') ||
+// document.getElementById('menu') ||
+// document.body
+
+// const onceObserver = new MutationObserver(() => {
+// const source = document.querySelector('#materials\\.extra_container')
+// if (source) {
+// // stop the "finder" observer
+// onceObserver.disconnect()
+
+// // Move whatever is present right now
+// moveAcceptedChildrenOnce()
+
+// // Keep mirroring anything Potree adds later
+// observeAndMirrorExtraContainer()
+// }
+// })
+
+ //onceObserver.observe(menu, { childList: true, subtree: true })
+}
diff --git a/src/potreeViewer.js b/src/potreeViewer.js
index fb709f0..dd81e01 100644
--- a/src/potreeViewer.js
+++ b/src/potreeViewer.js
@@ -1,5 +1,9 @@
-import { initElevationControls } from './ElevationControl/elevationControl.js'
+import {
+ initElevationControls,
+ autoSelectFirstPointCloud
+} from './ElevationControl/elevationControl.js'
import { initMeasurementsPanel } from './MeasurementControl/measurementsPanel.js'
+import { initAcceptedControls, toggleAcceptedLegend } from './Accepted/accepted.js'
import { ecef } from './config.js'
/**
@@ -35,7 +39,54 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) {
$('#menu_filters').next().show()
viewer.toggleSidebar()
- initElevationControls(viewer)
+ initElevationControls(viewer, {
+ onActivateElevation: () => {
+ const pc = viewer.scene.pointclouds[0]
+ if (!pc) {
+ console.warn('[Elevation] No point cloud loaded yet.')
+ return
+ }
+
+ // Switch to elevation coloring
+ pc.material.activeAttributeName = 'elevation'
+ pc.material.gradient = Potree.Gradients['VIRIDIS']
+ // Build/refresh Potree's Materials/Elevation UI in the sidebar
+ autoSelectFirstPointCloud()
+ // One-shot render because default loop is disabled
+ viewer.render()
+ }
+ })
+
+ initAcceptedControls(viewer, {
+ onActivateAccepted: () => {
+ const pc = viewer.scene.pointclouds[0]
+ if (!pc) {
+ console.warn('[Accepted] No point cloud loaded yet.')
+ return
+ }
+ // Switch to attribute-based coloring using the 'accepted' attribute
+ pc.material.activeAttributeName = 'accepted'
+ pc.material.gradient = Potree.Gradients['GRAYSCALE']
+ pc.material.gradientRange = [0, 1]
+
+ // Ensure Materials UI is present/updated (your Elevation panel pattern)
+ // If you have a shared autoSelectFirstPointCloud(), call it here.
+ const cloudIcon = document.querySelector(
+ '#scene_objects i.jstree-themeicon-custom'
+ )
+ if (cloudIcon) {
+ cloudIcon.dispatchEvent(new MouseEvent('click', { bubbles: true }))
+ }
+
+
+ // One-shot render since default loop is disabled
+ viewer.render()
+
+ toggleAcceptedLegend(true)
+
+ }
+ })
+
initMeasurementsPanel(viewer)
})
@@ -58,8 +109,6 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) {
pc.material.activeAttributeName = 'elevation'
pc.material.gradient = Potree.Gradients['VIRIDIS']
- e.pointcloud.projection = ecef
-
// Initialize camera position and target point (manually chosen)
viewer.scene.view.setView(
[1993552.9, 87954.487, 7134018.721], // Initial camera position