Skip to content

Commit c22a80f

Browse files
committed
Add recursive multi-level cave support (nested Therion/Survex hierarchies)
Make Cave recursive (children[] of sub-caves; surveys stay leaves) so nested Therion `survey` / Survex `*begin` hierarchies are preserved as one connected cave with sub-caves. Station-map keys become survey-qualified (name@surveyPath) so reused station numbers stay distinct; bare names are recovered for display. Importers: - assembleCave builds the nested Cave tree and distributes stations / aliases / comments / dimensions per cave node; captures source provenance. - Survex: *case support (default lowercase, case-insensitive matching) and conversion of dotted survey.station equate/fix refs to the internal @-form; capture of top-level *fix. - Recursive directory import (Open Cave Folder) with directory-aware include resolution. Solver (SurveyHelper): - Order-independent fixpoint with multi-fix seeding and a deterministic origin for un-georeferenced caves; transitive equate resolution; mutually-exclusive alias placement (no duplicate junctions). - recalculateCave moved into SurveyHelper (pure solve + distribute). Scene / tools / exports are tree- and qualified-key aware: the section graph bridges equates (shortest path, cycles, color-by-distance), per-cave color resolves the owning sub-cave, DXF/SVG/KML/PDF/Polygon use qualified lookups with bare display, the attribute editor shows bare names, and station details list the full cave chain. Persistence: cave/survey colours round-trip; nested children are serialized inside the parent cave record. Performance: shot edits and survey reordering no longer trigger a full network recompute on large systems. Hungarian manual updated; extensive unit tests added (cave tree, fixpoint solver, qualified-key consumers, Survex case/refs, include resolution, per-cave colour).
1 parent 4908518 commit c22a80f

56 files changed

Lines changed: 4348 additions & 1132 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

css/sidebar.css

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,13 @@
411411
.explorer-tree-node {
412412
display: flex;
413413
align-items: center;
414-
padding: 4px 0;
414+
/* Right padding matches the cave header (.models-tree-category-header: 8px 12px) so the
415+
trailing ⋮ menu and 👁 visibility icons line up vertically between sub-caves and
416+
sub-surveys in a nested tree. */
417+
padding: 4px 12px 4px 0;
415418
cursor: pointer;
416419
user-select: none;
420+
gap: 8px;
417421
}
418422

419423
.explorer-tree-node:hover {
@@ -475,11 +479,11 @@
475479
.explorer-tree-visibility {
476480
width: 16px;
477481
height: 16px;
478-
margin-left: 8px;
479482
cursor: pointer;
480483
opacity: 0.7;
481-
margin-right: 10px;
482-
margin-bottom: 5px;
484+
flex-shrink: 0;
485+
/* No own margins: spacing comes from the row's flex `gap` and right padding, so the icon
486+
aligns identically on cave-header rows and survey rows. */
483487
}
484488

485489
.explorer-tree-visibility:hover {
@@ -1319,9 +1323,11 @@ body {
13191323
text-align: center;
13201324
}
13211325

1322-
/* Children container */
1326+
/* Children container. No right padding: child rows (survey nodes and nested cave headers)
1327+
carry their own 12px right padding, so their trailing ⋮/👁 icons line up with the parent
1328+
cave header's icons instead of being inset. */
13231329
.models-tree-children {
1324-
padding: 4px 8px 8px 8px;
1330+
padding: 4px 0 8px 8px;
13251331
}
13261332

13271333
/* Model node styles */

docs/plan-master-file-chooser.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Master-file chooser for directory / multi-file Survex & Therion imports
2+
3+
## Context
4+
5+
A real Survex/Therion project folder often contains **several "master" files that describe the
6+
same caves multiple times** — e.g. the Migovec dataset
7+
(`/tmp/svx-test/migovecsurveydata/migovecsurveydata/`) has `system_migovec.svx`, `mig.svx`,
8+
`sistem_migovec_2017.svx`, `primadona_ubend_monatip.svx`, … each `*include`-ing overlapping
9+
sub-files, so the same caves appear ~4×.
10+
11+
Because the browser cannot read the filesystem, the user can't select one master `.svx` and have
12+
the app follow its `*include`s (the included files aren't loaded). Their only options are:
13+
- **Directory pick** (`#caveDirInput`, `webkitdirectory`) — loads *all* files, but
14+
`findRootFile` (`src/io/cave-survey-helpers.js:140`) auto-picks the single highest-ranked master
15+
(`ranked[0]`), giving the user no say in *which* one.
16+
- **Multi-file pick** (`#caveInput`) — can't follow `*include`s across files the user didn't pick.
17+
18+
**Goal:** after a directory (or multi-file) import, when more than one candidate master is
19+
detected, show a chooser so the user picks which master(s) to import; then import **only the
20+
chosen master('s) `*include`/`input` closure**, ignoring the duplicate masters. This bridges the
21+
browser limitation: pick the folder (all files in memory) → pick the master(s) → import its tree.
22+
23+
Decisions (confirmed with user):
24+
- **Multi-select** chooser (checkboxes), top-ranked candidate pre-checked — lets the user import
25+
one variant *or* several genuinely-different caves in one action.
26+
- Chooser appears **only when ambiguous** (>1 candidate master). One master (or one master + its
27+
includes → 1 candidate) imports straight through, unchanged.
28+
29+
## Approach
30+
31+
`getCaves(textMap)` already parses from a single root via `#parseX(rootName, textMap)`
32+
`flattenFile(...)` which follows `*include`/`input` and pulls in **only referenced files**. So
33+
importing "just the chosen master's tree" is simply `getCaves(textMap, chosenRoot)` — the other
34+
masters in `textMap` are never referenced, so never parsed. No duplicates.
35+
36+
The chooser is a standard Promise-based modal (same pattern as
37+
`src/ui/encoding-selection-dialog.js` / `xyz-kind-dialog.js`, `dialog-overlay` CSS), shown from
38+
within `importFiles` (where `textMap` is built), so `getCaves`/`getCave` stay pure and
39+
deterministic for tests.
40+
41+
## Changes
42+
43+
### 1. `src/io/cave-survey-helpers.js` — expose all ranked candidates
44+
- Add `export function findRootFiles(textMap, opts)` that returns the **full ranked candidate
45+
list** as `[{ key, includeCount, title }]` (reuse the existing candidate/`ranked` logic from
46+
`findRootFile`, lines 149–187; `includeCount` = the `countPattern` match count already used for
47+
ranking; `title` = best-effort from `*title "…"` / `-title "…"` / first `*begin`/`survey` name,
48+
empty string if none).
49+
- Refactor `findRootFile` to `return findRootFiles(textMap, opts)[0]?.key ?? [...textMap.keys()][0]`
50+
so existing callers/behaviour are unchanged.
51+
- Add `export async function chooseRootImports(textMap, opts, dialog)`:
52+
- `const cands = findRootFiles(textMap, opts);`
53+
- `cands.length <= 1` → return `cands.map(c => c.key)` (0 ⇒ caller auto-detects; 1 ⇒ that key).
54+
- `> 1``const sel = await dialog.show(cands);` return `sel` (array of keys) or `null` if cancelled.
55+
56+
### 2. `src/ui/root-file-selection-dialog.js` — new modal (template: `encoding-selection-dialog.js`)
57+
- `class RootFileSelectionDialog { async show(candidates) }``Promise<string[] | null>`.
58+
- Renders a checkbox list of candidates (label = relative `key` + `title` + `(N includes)`), the
59+
first (top-ranked) pre-checked; `dialog-overlay` / `dialog-container dialog-content` markup;
60+
"Import selected" (resolves selected keys) and "Cancel" / Escape / overlay-click (resolves
61+
`null`). Disable "Import selected" when nothing is checked.
62+
63+
### 3. `src/io/therion-importer.js` & `src/io/survex-importer.js` — wire the chooser
64+
- Construct `this.rootFileDialog = new RootFileSelectionDialog();` (next to the existing
65+
`coordinateSystemDialog`).
66+
- Add optional `rootName` to `getCaves(textMap, rootName)`: if provided, parse from it
67+
(`#parseX(rootName, textMap)`); else `#findRootFile(textMap)` as today. `getCave` unchanged.
68+
- In `importFiles(filesMap, onCaveLoad)`, after building `textMap`:
69+
```
70+
const roots = await chooseRootImports(textMap, OPTS, this.rootFileDialog);
71+
if (roots === null) return; // user cancelled
72+
const targets = roots.length ? roots : [undefined]; // [] ⇒ auto-detect single tree
73+
for (const root of targets)
74+
for (const cave of await this.getCaves(textMap, root))
75+
if (cave) await onCaveLoad(cave);
76+
```
77+
(`OPTS` = `THERION_OPTS` / `SURVEX_OPTS`.) `src/main.js` `#importCaveFiles` is unchanged.
78+
79+
### 4. i18n — `src/i18n/translations/en.json` + `hu.json`
80+
- Add `ui.panels.rootFileSelection`: `title`, `message`, `importSelected`, `cancelled`
81+
(follow the `ui.panels.encodingSelection` convention, single-brace `{param}`).
82+
83+
## Verification
84+
85+
- **Unit** (`tests/unit/survex.test.js`, mirror in a therion test):
86+
- `findRootFiles` returns multiple ranked candidates for a textMap with two unconnected
87+
masters; returns one for a normal single-master map.
88+
- `getCaves(textMap, rootKey)` imports **only** the chosen master's tree: build a map with
89+
master A (includes a1/a2) + master B (includes b1/b2); assert `getCaves(map,'A.svx')` yields
90+
only A's caves and none of B's.
91+
- `importFiles` with a stub `rootFileDialog` (`show: () => ['A.svx']`) over a 2-master map calls
92+
`onCaveLoad` only for A's caves; a stub returning `null` imports nothing.
93+
- **Live (chrome-devtools)**: open the Migovec folder via *Open Cave Folder* → chooser lists the
94+
candidate masters (checkboxes, top pre-checked) → check only `system_migovec.svx` → Import →
95+
exactly one System Migovec tree appears (no 4× duplicates). Re-open and select two distinct
96+
masters → both import. Confirm a normal single-master folder imports with **no** dialog.
97+
- `npm test` green (esp. existing Survex/Therion importer + `findRootFile` behaviour).

generate-pdf.sh

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/bin/bash
2+
3+
# Speleo Studio kézikönyv PDF generálása
4+
# Ez a script automatikusan létrehozza a teljes kézikönyvet egy PDF fájlban
5+
6+
echo "🚀 Speleo Studio kézikönyv PDF generálása..."
7+
echo "=========================================="
8+
9+
# Ellenőrizze, hogy a wkhtmltopdf telepítve van-e
10+
if ! command -v wkhtmltopdf &> /dev/null; then
11+
echo "❌ Hiba: wkhtmltopdf nincs telepítve!"
12+
echo ""
13+
echo "📥 Töltse le innen: https://wkhtmltopdf.org/downloads.html"
14+
echo ""
15+
echo "Telepítés után futtassa újra ezt a scriptet."
16+
exit 1
17+
fi
18+
19+
echo "✅ wkhtmltopdf telepítve van"
20+
21+
# Ellenőrizze, hogy a manual mappa létezik-e
22+
if [ ! -d "hu" ]; then
23+
echo "❌ Hiba: A 'hu' mappa nem található!"
24+
echo "Győződjön meg róla, hogy ezt a scriptet a projekt gyökérkönyvtárában futtatja."
25+
exit 1
26+
fi
27+
28+
echo "✅ Manual mappa megtalálható"
29+
30+
31+
echo "📁 Navigálás a manual mappába..."
32+
echo "🔄 PDF generálása folyamatban..."
33+
echo "${PWD}/manual/hu/index.html"
34+
# Generálja a PDF-et
35+
wkhtmltopdf \
36+
--enable-local-file-access \
37+
--page-size A4 \
38+
--margin-top 20mm \
39+
--margin-bottom 20mm \
40+
--margin-left 15mm \
41+
--margin-right 15mm \
42+
--print-media-type \
43+
--no-stop-slow-scripts \
44+
--javascript-delay 1000 \
45+
${PWD}/hu/index.html \
46+
../speleo-studio-teljes-kezikonyv.pdf
47+
48+
# Ellenőrizze, hogy a PDF sikeresen létrejött-e
49+
if [ -f "../speleo-studio-teljes-kezikonyv.pdf" ]; then
50+
echo ""
51+
echo "✅ Sikeres! PDF fájl létrehozva:"
52+
echo "📄 speleo-studio-teljes-kezikonyv.pdf"
53+
echo ""
54+
echo "📊 Fájlméret: $(du -h ../speleo-studio-teljes-kezikonyv.pdf | cut -f1)"
55+
echo ""
56+
echo "🎉 A teljes Speleo Studio felhasználói kézikönyv most elérhető PDF formátumban!"
57+
else
58+
echo ""
59+
echo "❌ Hiba történt a PDF generálása során."
60+
echo "Próbálja meg újra, vagy használja a böngésző alapú megoldást."
61+
echo ""
62+
echo "💡 Alternatív megoldás:"
63+
echo "1. Nyissa meg a Firefox böngészőt"
64+
echo "2. Nyissa meg: manual/index.html"
65+
echo "3. Nyomja meg: Ctrl+P (Cmd+P Mac-en)"
66+
echo "4. Válassza: 'PDF mentése' és 'Minden oldal'"
67+
fi
68+
69+
# Vissza a gyökérkönyvtárba
70+
cd ..
71+
72+
echo ""
73+
echo "📚 További információ: manual/single-pdf-guide.html"

index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
<body>
4545
<div class="file-upload">
4646
<input type="file" id="caveInput" accept=".cave,.json,.th,.svx,.3d" multiple style="display: none" />
47+
<!-- recursive directory import for multi-folder Therion/Survex projects -->
48+
<input type="file" id="caveDirInput" webkitdirectory directory multiple style="display: none" />
4749
<input type="file" id="surveyInput" accept=".csv" multiple style="display: none" />
4850
<!-- used in explorer tree to add a survey to a survey-->
4951
<input type="file" id="surveyInputPartial" accept=".csv" multiple style="display: none" />

manual/hu/01-bevezetes.html

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,18 +205,23 @@ <h4>Felfedező panel</h4>
205205
</p>
206206
<p>
207207
A felfedező panelen a barlang neve melletti háromszögre kattintva a barlang lenyílik és megjelennek alatta a
208-
felmérések. A háromszögre újra kattintva a lista záródik, újra csak a barlang neve látható. A barlang neve
209-
után következő szem (👁) ikon <strong>a barlang láthatóságát változtatja</strong>. A felmérések láthatósága
210-
szintén a nevük után következő szem (👁) ikon segítségével változik.
208+
felmérések. A háromszögre újra kattintva a lista záródik, újra csak a barlang neve látható. Egy barlang
209+
<strong>al-barlangokat</strong> (gyermek barlangokat) is tartalmazhat — ilyenkor a fa tetszőleges mélységig
210+
tagolódik, és az al-barlangok ugyanúgy kinyithatók. A barlang neve után következő szem (👁) ikon
211+
<strong>a barlang láthatóságát változtatja</strong>; ez al-barlangoknál a teljes alfára (minden gyermek
212+
barlangra és felmérésre) érvényesül. A felmérések láthatósága szintén a nevük után következő szem (👁) ikon
213+
segítségével változik.
211214
</p>
212215
<p>
213216
A felfedező panel tetején lévő kereső a <strong>barlangok és felmérések szűrésére </strong> szolgál (zöld
214217
keret az ábrán). Ha a kereső mező bal szélén a barlang mód van kiválasztva (lila omega az ábrán), a keresés
215218
a barlangok és felmérések neve alapján szűri a listát. Ha a tőle jobbra lévő pont kereső ikonra kattintunk,
216-
akkor a felmérések mérési adatainak egyezése alapján szűri a listát. Magyarán ha egy felmérésben
217-
"mellekag-1" es "mellekag-2" nevű mérés van, akkor a kereső mezőbe "mellekag"-at írva az adott felmérés
218-
biztosan megjelenik. Ez a funkció akkor hasznos, ha tudjuk egy mérési pont nevét, de nem tudjuk melyik
219-
felmérésben szerepel, viszont szeretnénk szerkeszteni az adatait (hossz, irány, ...)
219+
akkor a felmérések mérési adatainak (pont nevek) egyezése alapján szűri a listát. Magyarán ha egy
220+
felmérésben "mellekag-1" es "mellekag-2" nevű mérés van, akkor a kereső mezőbe "mellekag"-at írva az adott
221+
felmérés biztosan megjelenik. Ez a funkció akkor hasznos, ha tudjuk egy mérési pont nevét, de nem tudjuk
222+
melyik felmérésben szerepel, viszont szeretnénk szerkeszteni az adatait (hossz, irány, ...). Mindkét keresési
223+
mód <strong>tetszőleges mélységig</strong> keres a fában, így a mélyen, al-barlangokban található
224+
felméréseket és pontokat is megtalálja (a találat fölötti barlangok automatikusan kinyílnak).
220225
</p>
221226
<p>
222227
A kereső mező jobb oldalán található <strong>zöld + gomb</strong> egy <strong>új barlang</strong>

manual/hu/03-adatmodell.html

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ <h3>Barlangok</h3>
5353
<ul>
5454
<li>Saját nevet és metaadatokat tartalmaz (barlang adatlap)</li>
5555
<li>Egy vagy több felmérést tartalmazhat</li>
56+
<li>További <strong>al-barlangokat</strong> (gyermek barlangokat) is tartalmazhat tetszőleges mélységig</li>
5657
<li>Attribútumokat tartalmazhat</li>
5758
<li>Pont megjegyzéseket tartalmazhat</li>
5859
<li>Pont méreteket (LRUD) tartalmazhat</li>
@@ -106,9 +107,27 @@ <h4>Fix koordináták</h4>
106107
<p
107108
>A fix koordináták lehetővé teszik, hogy a barlangokat egy olyan koordináta rendszerben jelenítsük meg,
108109
amelyet más alkalmazások is használnak. Jelenleg az EOV és UTM vetületi koordináta rendszereket támogatja a
109-
Speleo Studio (csak a kezdőpont koordinátája adható meg), de fel lehet venni fix pontokat WGS84 (GPS)
110-
koordináták konvertálásával is.
110+
Speleo Studio, és egy barlangon belül <strong>több fix pont</strong> is megadható (jellemzően
111+
barlangrendszereknél, ahol minden résznek saját bejárati fix pontja van). Fel lehet venni fix pontokat WGS84
112+
(GPS) koordináták konvertálásával is.
111113
</p>
114+
115+
<h4>Al-barlangok (gyermek barlangok)</h4>
116+
<p
117+
>A Therion és a Survex egyetlen, tetszőlegesen mélyen egymásba ágyazható <code>survey</code> /
118+
<code>*begin</code> fogalmat használ: a magasabb szintek tipikusan barlangrendszereket vagy karsztterületeket,
119+
a mélyebb szintek pedig járatokat jelölnek. A Speleo Studio ezt a hierarchiát megőrzi: egy barlang
120+
tartalmazhat <strong>al-barlangokat</strong> és <strong>felméréseket</strong> is, az al-barlangok pedig
121+
további al-barlangokat — tetszőleges mélységig. A felmérés mindig levélelem (csak mérési adatokat
122+
tartalmaz), barlangot nem tartalmazhat.</p
123+
>
124+
<p
125+
>Az egy <code>equate</code> kapcsolatokkal összekötött (topológiailag összefüggő) barlangrendszer
126+
<strong>egyetlen barlangként</strong> töltődik be, a belső tagolás al-barlangokként jelenik meg a felfedező
127+
fában, amely tetszőleges mélységig kinyitható. Több, egymással nem összekötött barlangot tartalmazó fájl
128+
ezzel szemben <strong>külön-külön barlangokként</strong> töltődik be. A részletekért lásd
129+
<a href="04-adatok-importalasa.html">Adatok importálása</a>.</p
130+
>
112131
</div>
113132
</div>
114133

@@ -129,8 +148,8 @@ <h3>📏 Felmérések (Survey)</h3>
129148
<li><strong>Név:</strong> A felmérés azonosító neve</li>
130149
<li><strong>Dátum:</strong> A felmérés végrehajtásának dátuma</li>
131150
<li
132-
><strong>Induló pont:</strong> A felmérés kezdőpontja (csak a barlang első felmérésénél van figyelembe
133-
véve)</li
151+
><strong>Induló pont:</strong> A felmérés kezdőpontja. A felmérések sorrendje nem számít: a Speleo
152+
Studio sorrend-független módon számolja ki a pontok helyzetét (lásd lentebb).</li
134153
>
135154
<li><strong>Deklináció:</strong> A mágneses deklináció értéke</li>
136155
<li><strong>Mérőeszközök:</strong> Mérési eszközök listája</li>
@@ -143,7 +162,12 @@ <h3>📏 Felmérések (Survey)</h3>
143162
<div class="warning">
144163
<h3>⚠️ Fontos megjegyzések</h3>
145164
<ul>
146-
<li>A felmérés neve egyedi kell legyen</li>
165+
<li>A felmérés neve egy barlangon (vagy al-barlangon) belül egyedi kell legyen.</li>
166+
<li
167+
>Importált, többszintű adatoknál a mérési pontok nevei (pl. „1”, „2”) felmérésenként ismétlődhetnek — a
168+
Speleo Studio ezeket belsőleg felmérésenként egyedi módon tartja nyilván, így nem ütköznek. A
169+
megjelenített és exportált pontnevek mindig a rövid, eredeti nevek maradnak.</li
170+
>
147171
</ul>
148172
</div>
149173
</div>
@@ -347,6 +371,36 @@ <h3>Attribútumok</h3>
347371
</div>
348372
</div>
349373

374+
<h2>🧭 Pontok helyzetének kiszámítása (sorrend-független megoldó)</h2>
375+
376+
<p
377+
>A barlang 3D pontjait a Speleo Studio a mérésekből (hossz, irányszög, lejtés) számolja ki, kiindulva a
378+
rögzített (fix) pontokból. A számítás <strong>sorrend-független</strong>: nem számít, milyen sorrendben
379+
szerepelnek a felmérések a barlangban, az eredmény ugyanaz lesz. Ennek a működése a következő:</p
380+
>
381+
<ul>
382+
<li
383+
><strong>Több kiindulópont (fix):</strong> minden olyan felmérés, amely fix pontot tartalmaz, önállóan,
384+
abszolút koordinátán kerül elhelyezésre. Egy barlangrendszernél jellemzően több bejárati fix is van — mindegyik
385+
külön kiindulópontként szolgál.</li
386+
>
387+
<li
388+
><strong>Kapcsolódás equate-eken keresztül:</strong> a fix pontot nem tartalmazó felmérések akkor kerülnek a
389+
helyükre, amikor egy közös vagy <code>equate</code>-tel összekötött pontjuk már elhelyezett. A megoldó addig
390+
ismétli ezt, amíg már egyetlen felmérés sem tud újabb ponthoz kapcsolódni (fixpont-iteráció), így a
391+
többlépcsős equate-láncok is helyesen feloldódnak.</li
392+
>
393+
<li
394+
><strong>Sorrend átrendezése biztonságos:</strong> a felmérések átrendezése (húzd-és-ejtsd vagy „felülre
395+
mozgatás”) nem változtatja meg a pontok helyzetét — csak a felfedező fában megjelenő sorrendet —, ezért gyors
396+
és nem indít újraszámolást.</li
397+
>
398+
<li
399+
><strong>Nem kapcsolódó felmérések:</strong> ha egy felmérés semmilyen kiindulóponthoz nem köthető, „izolált”
400+
marad, a be nem helyezett méréseit pedig „árva” mérésként jelzi a program.</li
401+
>
402+
</ul>
403+
350404
<h2>🔄 Adatvalidáció és ellenőrzés</h2>
351405

352406
<p

0 commit comments

Comments
 (0)