Skip to content

Commit

Permalink
feat(#8): ✨ Made functionaliry for Accepted filtering
Browse files Browse the repository at this point in the history
Created a separate panel for accepted filtering. When the button is clicked the colors of the points are either black for not accepted and white for accepted points. The panel also have a description for this.
  • Loading branch information
Marie Wahlstrøm committed Oct 13, 2025
1 parent 4af9107 commit e2b778c
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 4 deletions.
7 changes: 7 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
rel="stylesheet"
href="/src/MeasurementControl/measurementsPanel.css"
/>
<link
rel = "stylesheet"
href = "src/Accepted/accepted.css"
/>
<link
rel = "stylesheet"
href = "src/ElevationControl/elevationControl.css"/>
</head>

<body>
Expand Down
56 changes: 56 additions & 0 deletions src/Accepted/accepted.css
Original file line number Diff line number Diff line change
@@ -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;
}
179 changes: 179 additions & 0 deletions src/Accepted/accepted.js
Original file line number Diff line number Diff line change
@@ -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 = '<span>Accepted Filter</span>'

const panel = document.createElement('div')
panel.className = 'pv-menu-list'
panel.innerHTML = '<div id="accepted_list" class="auto"></div>'

// 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 = `
<div class="legend-row">
<div class="legend-color accepted"></div>
<span>Accepted points</span>
</div>
<div class="legend-row">
<div class="legend-color not-accepted"></div>
<span>Not accepted points</span>
</div>
`
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 <li> 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 })
}
57 changes: 53 additions & 4 deletions src/potreeViewer.js
Original file line number Diff line number Diff line change
@@ -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'

/**
Expand Down Expand Up @@ -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)
})

Expand All @@ -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
Expand Down

0 comments on commit e2b778c

Please sign in to comment.