Projects can now be opened from the deck: a modal shows live progress per section, the services on site and a plan map drawn from the site model. - ProjectModal with per-section progress and site maps (src/data/sitemaps), generated by app/scripts/build-sitemaps.py - progress.json holds the per-project section states - Why Mega becomes a scroll-driven list with a sticky panel that follows the point in the middle of the screen, plus new pillar photographs - PRJ-06 Munyaka, PRJ-07 Cedar Gate and PRJ-08 The Hills join the deck, so the track length and the site count now follow the number of projects - Projects moves above Why Mega and the section labels renumber - Contact section rebuilt on an asymmetric grid - Intro counters keep their thousands separator and no longer wrap - Dev server listens on the LAN so a phone on the same Wi-Fi can open the site
43 lines
1.9 KiB
Python
43 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Builds the small public plan maps used by the project popup.
|
|
|
|
Reads a site model's bushveld.json (road/paving areas grouped by section) and
|
|
writes src/data/sitemaps/<slug>.json: sections in build order, each with
|
|
simplified outline paths in a y-down viewBox. No levels, quantities or costs.
|
|
"""
|
|
import json, sys, pathlib
|
|
|
|
MODELS = pathlib.Path.home() / 'DEVELOPMENT/MEGAIFRASTRUCTURE/reports/site-models'
|
|
SOURCES = {
|
|
'munyaka': MODELS / 'munyaka/2026-10-28/bushveld.json',
|
|
'cedar-gate': MODELS / 'cedar-gate/2026-10-20/bushveld.json',
|
|
# Only stored in the app's database; exported from the active site_models record.
|
|
'the-hills': pathlib.Path(__file__).resolve().parent / 'source/hills-bushveld.json',
|
|
}
|
|
OUT = pathlib.Path(__file__).resolve().parent.parent / 'src/data/sitemaps'
|
|
|
|
def build(slug, src):
|
|
d = json.load(open(src))
|
|
order = [s['key'] for s in d['sections']]
|
|
names = {s['key']: s.get('name') or f"Chainage {s['from']:g}-{s['to']:g} m" for s in d['sections']}
|
|
rings = {k: [] for k in order}
|
|
for a in d['areas']:
|
|
if a['section'] in rings:
|
|
rings[a['section']] += a['rings']
|
|
pts = [p for rs in rings.values() for r in rs for p in r]
|
|
x0, x1 = min(p[0] for p in pts), max(p[0] for p in pts)
|
|
y0, y1 = min(p[1] for p in pts), max(p[1] for p in pts)
|
|
pad = max(x1 - x0, y1 - y0) * 0.04
|
|
w, h = x1 - x0 + 2 * pad, y1 - y0 + 2 * pad
|
|
def path(ring):
|
|
return 'M' + 'L'.join(f'{p[0]-x0+pad:.1f} {y1-p[1]+pad:.1f}' for p in ring[:-1]) + 'Z'
|
|
sections = [
|
|
{'key': k, 'name': names[k], 'd': ' '.join(path(r) for r in rings[k])}
|
|
for k in order if rings[k]
|
|
]
|
|
json.dump({'viewBox': [0, 0, round(w, 1), round(h, 1)], 'sections': sections},
|
|
open(OUT / f'{slug}.json', 'w'), separators=(',', ':'))
|
|
print(slug, len(sections), 'sections', round(w), 'x', round(h))
|
|
|
|
for slug, src in SOURCES.items():
|
|
build(slug, src)
|