feat: Add fence functionality with 3D model integration and site controls
continuous-integration/drone/push Build is passing

This commit is contained in:
martin.fencl
2026-10-26 14:25:31 +01:00
parent 222fb6f6f7
commit 9defe3ac98
6 changed files with 292 additions and 12 deletions
+13 -9
View File
@@ -312,7 +312,7 @@ let backend=null,projectPanel=null;
}
// Independent site layers: initSiteControls enables all three on each fresh open.
const site = { road: false, parcel: false, parking: false, savedCamera: null, lastFocus: null };
const site = { road: false, parcel: false, parking: false, fence: false, savedCamera: null, lastFocus: null };
const siteActive = () => site.road || site.parcel || site.parking;
function meshVisible(m) {
if (m.legacySite) return false;
@@ -322,6 +322,8 @@ let backend=null,projectPanel=null;
case 'parcel': case 'boundary': return showTerrain && site.parcel;
case 'road': return site.road;
case 'parking': return site.parking;
// Each fence part stands on one terrain slab and is shown only together with it.
case 'fence': return showTerrain && site.fence && (m.std?.fenceRegion === 'parcel' ? site.parcel : m.std?.fenceRegion === 'north' ? siteActive() : true);
default: return showTerrain || !m.context;
}
}
@@ -363,18 +365,20 @@ let backend=null,projectPanel=null;
setCamera(fittedSiteCamera(mode), label, instant);
}
function setSiteLayer(key, on, focus = true) {
if (!['road', 'parcel', 'parking'].includes(key)) return;
if (!['road', 'parcel', 'parking', 'fence'].includes(key)) return;
// The fence is only shown or hidden: it does not count as active surroundings and keeps the camera.
const surroundings = key !== 'fence';
const was = siteActive();
if (!was && on) site.savedCamera = { theta, phi, distance, target: [...target], ortho: orthographic, label: $('#directionText').textContent };
if (surroundings && !was && on) site.savedCamera = { theta, phi, distance, target: [...target], ortho: orthographic, label: $('#directionText').textContent };
site[key] = !!on;
const ids = { road: 'siteRoad', parcel: 'siteParcel', parking: 'siteParking' };
const ids = { road: 'siteRoad', parcel: 'siteParcel', parking: 'siteParking', fence: 'siteFence' };
const input = $('#' + ids[key]); input.checked = site[key];
input.closest('label').classList.toggle('selected', site[key]);
document.body.classList.toggle('site-active', siteActive());
$('#siteMeta').hidden = !siteActive();
$('#siteMeta').textContent = 'C.3 + výškopis · mezi body interpolováno';
updateSolar();
if (focus) {
if (focus && surroundings) {
if (siteActive()) focusSite(on ? key : null);
else if (site.savedCamera) {
setCamera(site.savedCamera, site.savedCamera.label || 'Dům');
@@ -407,20 +411,20 @@ let backend=null,projectPanel=null;
}
function initSiteControls() {
for (const [id, key] of [['siteRoad', 'road'], ['siteParcel', 'parcel'], ['siteParking', 'parking']]) {
for (const [id, key] of [['siteRoad', 'road'], ['siteParcel', 'parcel'], ['siteParking', 'parking'], ['siteFence', 'fence']]) {
const input = $('#' + id); input.onchange = () => setSiteLayer(key, input.checked);
setSiteLayer(key, true, false);
setSiteLayer(key, key !== 'fence', false);
}
$('#siteFit').onclick = () => focusSite();
window.SITE_SIM = {
getState: () => ({
road: site.road, parcel: site.parcel, parking: site.parking, terrain: showTerrain,
road: site.road, parcel: site.parcel, parking: site.parking, fence: site.fence, terrain: showTerrain,
visibleObjects: meshes.filter(visible).length, referenceElevation: 591.840,
projectArea: 1010, digitizedArea: 987.89, houseGeometryUnchanged: true
}),
setLayer: (key, on, focus = true) => setSiteLayer(key, on, focus),
focus: mode => focusSite(mode),
reset: () => { for (const key of ['road', 'parcel', 'parking']) setSiteLayer(key, false, false); setPreset('terrace'); },
reset: () => { for (const key of ['road', 'parcel', 'parking', 'fence']) setSiteLayer(key, false, false); setPreset('terrace'); },
getCamera: () => ({ theta, phi, distance, target: [...target], aspect }),
checkGround: () => gltf.extras.siteStudy.finishedGroundC3
};