- Bouton "Supprimer" sur chaque run (liste + detail), DELETE deja expose cote API. - Tous les parametres CloudCompare mirroires dans l'UI (mirroir de la boite "Compute Volume" du GUI) : strategie de remplissage (leave_empty/min/max/custom/interpolate/kriging), hauteur de cellule (moyenne/min/max/mediane, -PROJ), direction de projection (X/Y/Z, -VERT_DIR), multiplicateur de grille. Nuage comble desormais exporte en ASCII (pas .bin) : -VOLUME le lit nativement, evite un aller-retour inutile puisqu'il faut de toute facon un export ASCII pour la carte de comparaison. - Tableau : volume/surface/volume ajoute/volume retire avec % absolu et % relatif vs niveau 0. Matching cells retire du tableau et des graphiques (mais toujours calcule/stocke, juste plus affiche). - Carte de comparaison unique par niveau (bleu->rouge façon CloudCompare, colormap jet) a la place des heightmaps separees par nuage : diff = ceil - ground (ou ceil - plan constant), calculee nous-memes par binning numpy sur une grille commune. - README + CLAUDE.md : avertissement explicite sur la persistance Coolify quand le build pack est "Application"/Dockerfile plutot que "Docker Compose" (docker-compose.yml et son volume nomme sont alors completement ignores par Coolify, il faut un volume "Storages" configure manuellement). - Valide en local avec les 2 vrais nuages utilisateur (mode comparaison) : resultats identiques a avant (14 244 -> 14 722 m3 selon le niveau), cartes de comparaison generees a chaque niveau, bouton suppression teste via l'UI.
304 lines
23 KiB
Markdown
304 lines
23 KiB
Markdown
# CLAUDE.md
|
|
|
|
Notes pour toute future session travaillant sur ce repo. Ce fichier documente des faits
|
|
verifies empiriquement sur CloudCompare 2.13.2 (Windows officiel ET paquet apt Debian trixie) —
|
|
pas de la documentation officielle recopiee, mais des comportements observes directement, souvent
|
|
non documentes ou contredisant la doc/le wiki.
|
|
|
|
## Contexte du projet
|
|
|
|
Pipeline : nuage de points (LAS/LAZ/COPC.laz/BIN) -> calcul de volume 2.5D CloudCompare -> decimation
|
|
spatiale progressive x2 sur 5 etapes (chainee, chaque niveau decime depuis le precedent, pas depuis
|
|
l'original) -> volume recalcule a chaque niveau -> assemblage des 6 nuages dans un seul `.bin` ->
|
|
rapport CSV/JSON + images. UI web (NestJS + Tailwind CDN) avec historique des runs (SQLite).
|
|
E57 volontairement hors perimetre.
|
|
|
|
## CloudCompare CLI — comportements verifies
|
|
|
|
### `-VOLUME` (calcul de volume 2.5D)
|
|
|
|
- Sur un nuage **unique**, utiliser `-CONST_HEIGHT <valeur>` comme plan de reference (le nuage charge
|
|
est le "ceiling", le plan constant est le "ground"). Sans `-CONST_HEIGHT`, la commande attend DEUX
|
|
nuages charges.
|
|
- Syntaxe complete : `-VOLUME -GRID_STEP <val> [-VERT_DIR 0/1/2] [-CONST_HEIGHT <val>] [-GROUND_IS_FIRST] [-OUTPUT_MESH]`.
|
|
- **Piege majeur non documente** : le rapport texte `VolumeCalculationReport_<date>.txt` n'est genere
|
|
QUE si `-AUTO_SAVE ON` (le defaut). Avec `-AUTO_SAVE OFF` (qu'on utilise partout ailleurs pour
|
|
eviter de polluer le dossier de travail avec des fichiers intermediaires), la commande `-VOLUME`
|
|
s'execute (log "[2.5D VOLUME CALCULATION] finished") mais **aucun rapport n'est ecrit, aucune
|
|
erreur n'est loggee**. Il faut explicitement repasser `-AUTO_SAVE ON` juste pour l'appel `-VOLUME`.
|
|
Voir `CcRunnerService.run(args, cwd, { autoSave: true })`.
|
|
- Avec `-AUTO_SAVE ON`, `-VOLUME` sauvegarde AUSSI automatiquement une grille de difference de hauteur
|
|
nommee `<nom>_HEIGHT_DIFFERENCE_<date>.bin` qu'on ne veut pas garder — on la detecte (meme technique
|
|
que pour le rapport, cf ci-dessous) et on la supprime juste apres (voir pipeline.service.ts).
|
|
- **CloudCompare ecrit ses fichiers de sortie automatiques (rapport, grilles) relativement au dossier
|
|
du fichier charge via `-O`, pas au `cwd` du process.** Si on charge `-O levels/L0_full.bin`, le
|
|
rapport atterrit dans `levels/VolumeCalculationReport_*.txt`, pas a la racine du `cwd`. D'ou la
|
|
necessite d'une recherche **recursive** (`CcRunnerService.findLatestFile`) plutot qu'un simple
|
|
`readdir` du dossier de travail.
|
|
- Format du rapport texte (verifie, stable) — cle:valeur, une ligne par info, parsable par regex :
|
|
```
|
|
Volume: 7.834176
|
|
Surface: 100.774998
|
|
----------------------
|
|
Added volume: (+)7.975798
|
|
Removed volume: (-)0.141622
|
|
----------------------
|
|
Matching cells: 99.8%
|
|
Non-matching cells:
|
|
ground = 0.2%
|
|
ceil = 0.0%
|
|
Average neighbors per cell: 8.0 / 8.0
|
|
```
|
|
- **`Matching cells: X%`** est le meilleur signal natif de robustesse : c'est le % de cellules de la
|
|
grille de calcul ou les deux surfaces sont effectivement comparables. Il s'effondre des que la
|
|
decimation produit un nuage plus clairseme que la resolution de grille utilisee pour le calcul de
|
|
volume — exactement l'indicateur cherche pour ce projet (voir `matchingCellsWarnThreshold` dans
|
|
`config.ts`, defaut 90%).
|
|
- **`-GRID_STEP` doit etre adapte a CHAQUE niveau, pas fige sur la resolution du niveau 0.** Premiere
|
|
version du pipeline : un `GRID_STEP` unique (derive du niveau 0) reutilise sur les 6 niveaux "pour
|
|
rester comparable". En pratique ca fait exactement l'inverse de ce qu'on veut tester : une fois la
|
|
decimation plus grossiere que cette grille fixe, `matching cells %` s'effondre mecaniquement
|
|
(observe : 99% -> 58% -> 13% -> 3% -> 0.9% -> 0.2% sur 5 niveaux) parce que la grille est trop fine
|
|
pour les points restants, pas parce que le volume est vraiment devenu impossible a estimer. Ce n'est
|
|
pas un signal de robustesse utile, juste un artefact de resolution. **Fix retenu** : `GRID_STEP` par
|
|
niveau = pas spatial de CE niveau x `gridStepMultiplier` (le niveau 0 utilise `initialStep x
|
|
multiplier`). Chaque niveau a donc sa propre grille adaptee a sa propre densite de points — le
|
|
`matching cells %` refletera alors la vraie perte d'information due a la decimation, pas un
|
|
desalignement grille/densite. Voir `LevelResult.gridStep` (par niveau) et
|
|
`RunReport.params.gridStepMultiplier` (le seul parametre encore global).
|
|
|
|
- **`-VOLUME` n'a AUCUNE option de remplissage de trous (empty cell filling), meme si `-RASTERIZE`
|
|
en a.** Verifie en clonant les sources (`qCC/ccCommandRaster.cpp`, `CommandVolume25D::process`) :
|
|
l'appel a `ccVolumeCalcTool::ComputeVolume(...)` est fait avec `ccRasterGrid::LEAVE_EMPTY` **code en
|
|
dur** pour le ground ET le ceil, non exposable via un flag CLI (`-EMPTY_FILL`, `-MAX_EDGE_LENGTH`
|
|
etc. existent bien comme constantes dans le fichier mais seule `CommandRasterize::process` les
|
|
consomme ; `CommandVolume25D::process` ne les lit jamais). **Contournement retenu** : avant chaque
|
|
`-VOLUME`, on rasterize+comble chaque nuage separement (`-RASTERIZE -GRID_STEP g -EMPTY_FILL INTERP
|
|
-MAX_EDGE_LENGTH m -OUTPUT_CLOUD -SAVE_CLOUDS FILE filled.bin`), on charge ensuite CE nuage comble
|
|
(pas l'original) dans `-VOLUME`. Verifie empiriquement (nuage synthetique avec trou d'occlusion
|
|
delibere, rayon 0.6m) : sans comblement, matching cells 96.4% / volume sous-estime ; avec
|
|
`-MAX_EDGE_LENGTH 0.3` (> diametre du trou), trou comble, matching cells 98.9% ; avec
|
|
`-MAX_EDGE_LENGTH 0.08` (< diametre du trou), AUCUN effet (identique au cas non comble) — confirme
|
|
que le parametre borne bien la portee du remplissage, empechant de combler les parties concaves du
|
|
contour d'un objet (exactement le risque signale par l'utilisateur). Voir
|
|
`PipelineService.prepareCloudAtLevel` (bloc "Remplissage des trous").
|
|
- **Piege associe** : `-RASTERIZE ... -OUTPUT_CLOUD` avec `-SAVE_CLOUDS FILE "a.bin b.bin"` (2 noms)
|
|
echoue avec `Invalid parameter: specified 2 file names, but there are 1 clouds` — le nuage comble
|
|
**remplace** le nuage original dans la liste des entites chargees (pas un ajout), il ne faut donc
|
|
fournir qu'UN SEUL nom de fichier a `-SAVE_CLOUDS FILE`.
|
|
- Le nuage "comble" est un nuage-grille (un point par cellule non vide, ex: 39971 points pour une
|
|
grille 201x201 avec quelques cellules hors de l'enveloppe convexe) — PAS le nuage original avec des
|
|
points ajoutes. On le garde uniquement pour le calcul de volume et la carte de comparaison
|
|
(`raw/L{n}_filled.xyz`, en **ASCII** — pas `.bin` : `-VOLUME` lit l'ASCII nativement, et ca evite
|
|
un aller-retour BIN inutile puisqu'on a de toute facon besoin d'un export ASCII pour la carte de
|
|
comparaison Python). Le nuage BRUT (non comble, en `.bin`) reste utilise pour l'assemblage final et
|
|
le chainage de decimation, afin de ne pas faire chainer l'interpolation d'un niveau vers le suivant
|
|
(qui composerait l'erreur).
|
|
- **Toutes les strategies `-EMPTY_FILL` de CloudCompare sont exposees dans l'UI** (mirroir de la
|
|
boite de dialogue "Compute Volume" du GUI), pas seulement INTERP : `LEAVE_EMPTY` (aucun flag),
|
|
`MIN_H`/`MAX_H` (hauteur min/max des cellules voisines), `CUSTOM_H` (necessite `-CUSTOM_HEIGHT
|
|
<valeur>`), `INTERP` (necessite `-MAX_EDGE_LENGTH <valeur>`), `KRIGING` (necessite `-KRIGING_KNN
|
|
<n>`). Voir `emptyFillArgs()` dans `pipeline.service.ts`.
|
|
- **`-PROJ <AVG|MIN|MAX|MED>`** (type de projection par cellule, "Hauteur de cellule" dans le GUI) et
|
|
**`-VERT_DIR <0|1|2>`** (direction de projection X/Y/Z) sont egalement exposes. Contrairement a
|
|
`-EMPTY_FILL`, `-VOLUME` accepte bien `-VERT_DIR` nativement (verifie dans les sources) — on le
|
|
passe donc aux DEUX commandes (`-RASTERIZE` pour le remplissage, `-VOLUME` pour le calcul) afin
|
|
qu'elles restent coherentes. `-PROJ` en revanche n'existe QUE pour `-RASTERIZE` (comme
|
|
`-EMPTY_FILL` : `-VOLUME` a `PROJ_AVERAGE_VALUE` code en dur) — mais comme le nuage envoye a
|
|
`-VOLUME` est deja notre grille-comble (1 point/cellule), le "moyennage" interne de `-VOLUME`
|
|
devient une passe-plat : c'est bien NOTRE `-PROJ` (au moment du remplissage) qui determine le
|
|
resultat final, pas celui hardcode dans `-VOLUME`. `INV_VAR` (variance inverse) n'est pas expose :
|
|
necessite un champ scalaire dedie qu'on n'a pas dans ce pipeline.
|
|
|
|
- **Piege critique de parsing** : CloudCompare formate les **grands nombres avec une virgule comme
|
|
separateur de milliers** dans le rapport texte (`Volume: 14,244.464657`, pas `14244.464657`). Une
|
|
regex naive `[-\d.eE]+` s'arrete a la virgule et tronque silencieusement la valeur (14 244 devient
|
|
**14**, sans aucune erreur) — bug reel decouvert en testant avec un vrai nuage industriel (volume
|
|
affiche a tort ~50m3 au lieu de ~14 244m3 avant fix, meme si dans ce cas precis c'etait en plus
|
|
combine a un mauvais choix de plan de reference, voir plus bas). **Fix** : regex `[-\d,.eE]+` puis
|
|
`.replace(/,/g, '')` avant `Number(...)`. Voir `CcRunnerService.parseVolumeReportFile` et le test
|
|
`cc-runner.service.spec.ts` ("grands volumes avec separateur de milliers").
|
|
|
|
### `-SS SPATIAL` / `-SS RANDOM` (sous-echantillonnage)
|
|
|
|
- `-SS SPATIAL <distance>` : distance minimale entre points, PAS un ratio de points cible. Le nombre
|
|
de points obtenu depend de la densite reelle du nuage — jamais garanti a un pourcentage exact.
|
|
- `-SS RANDOM <n>` : sous-echantillonne a `n` points. Si `n` >= nombre de points du nuage, ne plante
|
|
pas (comportement observe : garde le nuage tel quel).
|
|
- Log de sortie a parser : `[SUBSAMPLE]` puis `Result: <N> points` (regex utilisee :
|
|
`/\[SUBSAMPLE\][\s\S]*?Result:\s*(\d+)\s*points/`).
|
|
- `-AUTO_SAVE OFF` fonctionne correctement ici (contrairement a `-VOLUME`) : pas de fichier auto-sauve
|
|
parasite, seul `-SAVE_CLOUDS FILE "nom.bin"` explicite produit un fichier, avec le nom voulu.
|
|
|
|
### `-SAVE_CLOUDS`
|
|
|
|
- `-SAVE_CLOUDS FILE "nom.ext"` : nom de sortie explicite (fonctionne, contrairement a ce que
|
|
certains threads du forum CloudCompare laissent penser).
|
|
- `-SAVE_CLOUDS ALL_AT_ONCE FILE "nom.bin"` avec **plusieurs nuages charges** (plusieurs `-O`) sans
|
|
`-MERGE_CLOUDS` : sauvegarde tous les nuages comme entites **distinctes** dans un seul `.bin` (le
|
|
format `.bin` de CloudCompare supporte nativement une hierarchie de plusieurs nuages). Verifie en
|
|
rouvrant le fichier : chaque nuage retrouve son point count exact, sans fusion de geometrie. C'est
|
|
la technique utilisee pour assembler les 6 niveaux de decimation dans un seul fichier de sortie.
|
|
**`-MERGE_CLOUDS` fusionnerait la geometrie en un seul nuage — a ne PAS utiliser ici.**
|
|
- Pas de commande `-RENAME_CLOUDS` (n'existe pas, testee = "Unknown or misplaced command"). Le nom de
|
|
chaque nuage dans le `.bin` fusionne est derive du nom de fichier source charge — on nomme donc les
|
|
fichiers intermediaires de facon descriptive (`L0_full.bin`, `L1_step0.05.bin`, ...) plutot que de
|
|
chercher a renommer les entites apres coup.
|
|
|
|
### Autres commandes
|
|
|
|
- `-RASTERIZE -GRID_STEP <val> -OUTPUT_RASTER_Z` : export GeoTIFF. **Fonctionne sur le build Windows
|
|
officiel, mais PLANTE (assertion `false` dans `ccRasterizeTool.cpp:ExportGeoTiff`, core dump) sur le
|
|
paquet apt Debian trixie**, faute de support GDAL compile dans ce paquet. Ne pas utiliser cette
|
|
voie dans l'image Docker — voir section "Paquet apt Debian" ci-dessous pour la solution retenue.
|
|
- Les commandes s'appliquent comme une **machine a etats sequentielle sur UN SEUL process** : on ne
|
|
peut pas "reprendre" une session precedente. Chaque etape logique du pipeline (stats, conversion,
|
|
decimation, volume, export) = un appel CLI independant, avec son propre `-O` de rechargement.
|
|
- `-SILENT` doit etre le tout premier argument (ou juste apres `-VERBOSITY`).
|
|
- `-PREC <n>` controle la precision decimale des exports ASCII (`-C_EXPORT_FMT ASC`).
|
|
|
|
## Mode comparaison a 2 nuages (`cloud_compare`)
|
|
|
|
En plus du mode `const_height` (nuage unique vs plan Z constant), un second mode compare 2 nuages
|
|
entre eux : le nuage du **haut** ("top", surface superieure de l'amas) et celui du **bas** ("bottom",
|
|
limite inferieure/base/socle). Motivation reelle : sur un vrai nuage industriel ("Amas 1 Nuage.las",
|
|
~150m de long), le mode `const_height` donnait un volume de ~50m3 alors que l'objet fait clairement
|
|
plus — cause identifiee par inspection de la heightmap : le nuage a une empreinte **diagonale** dans
|
|
sa bounding box (~150x113m), et surtout le plan Z constant (altitude min globale du nuage entier)
|
|
n'a aucun rapport avec la base reelle de l'amas si le fichier contient aussi du terrain environnant.
|
|
Avec le second nuage ("Plan d'ajustement NUAGE.las", une base fittee specifiquement sous l'amas), le
|
|
volume calcule est de ~14 244m3, stable a +/-4% meme a 16x de decimation (129 points) — la aussi
|
|
confirme par inspection visuelle (les deux nuages ont exactement la meme empreinte au sol).
|
|
**Enseignement general : le mode `const_height` n'est fiable QUE si le nuage ne contient QUE l'objet
|
|
mesure (pas de terrain/contexte environnant) ; sinon `cloud_compare` avec une base dediee est le seul
|
|
mode qui donne un chiffre correct.**
|
|
|
|
Implementation :
|
|
- **Appariement de densite** avant le niveau 0 : on calcule la distance mediane au plus proche voisin
|
|
de chaque nuage independamment, on prend le MAX des deux (`matchedSpacing`), puis on decime (une
|
|
seule fois, `-SS SPATIAL matchedSpacing`) le nuage le plus dense pour le ramener a la meme resolution
|
|
que l'autre — l'operation est un no-op si le nuage est deja plus clairseme que `matchedSpacing` (`-SS
|
|
SPATIAL` ne peut pas densifier). Le niveau 0 du mode `cloud_compare` EST ce nuage appari (contrairement
|
|
au mode `const_height` ou le niveau 0 est le nuage brut, jamais decime) : `spatialStep(niveau) =
|
|
initialStep * facteur^niveau` pour TOUS les niveaux 0..5 en mode compare (vs `null` puis
|
|
`initialStep * facteur^niveau` pour niveaux 1..5 en mode const_height).
|
|
- **Ordre de chargement pour `-VOLUME`** : `-O <top_comble.bin> -O <bottom_comble.bin> -VOLUME
|
|
-GRID_STEP g` (sans `-CONST_HEIGHT`, sans `-GROUND_IS_FIRST`). Verifie dans les sources
|
|
(`CommandVolume25D::process`) : le premier nuage charge = "ceil", le second = "ground" (sauf
|
|
`-GROUND_IS_FIRST` qui les inverse) — charger top puis bottom donne directement ceil=top,
|
|
ground=bottom, ce qui correspond au sens voulu.
|
|
- Remplissage des trous (voir section `-VOLUME` plus haut) applique aux DEUX nuages independamment,
|
|
a chaque niveau, avec le meme `gridStep`/`maxEdgeLength` adaptatifs que le mode single-cloud.
|
|
- La decimation des DEUX nuages est synchronisee (meme `spatialStep` a chaque niveau) ; si l'un des
|
|
deux tombe sous `minPointsToContinue`, toute la progression s'arrete (les deux surfaces sont
|
|
necessaires pour une comparaison valide).
|
|
|
|
## Paquet apt Debian `cloudcompare` (trixie, 2.13.2) — limitations et contournements
|
|
|
|
Choix d'archi : `debian:trixie-slim` + `apt-get install cloudcompare` plutot qu'une compilation depuis
|
|
les sources (des heures de build, fragile). Debian trixie est la seule distro testee avec une version
|
|
recente (2.13.2) directement en apt `main` (Ubuntu jammy/noble n'ont que 2.11.3 en `universe`).
|
|
Mais ce paquet est **allege par rapport au build officiel Windows** :
|
|
|
|
1. **Pas de plugin LAS/LAZ** (`dpkg -L cloudcompare` ne montre que
|
|
`libQCORE_IO_PLUGIN.so`, aucun `QLAS_IO_PLUGIN`). Ouvrir un `.las`/`.laz` donne :
|
|
`[Load] Can't guess file format: unhandled file extension 'las'`.
|
|
**Solution retenue** : conversion LAS/LAZ/COPC -> ASCII XYZ via `laspy` (Python, `python/las_to_xyz.py`)
|
|
*avant* tout traitement CloudCompare. L'import ASCII XYZ, lui, fonctionne nativement sans aucun
|
|
plugin (verifie : `CloudCompare -O fichier.xyz` marche directement sur ce paquet). Un `.copc.laz`
|
|
est lu par laspy comme un LAZ standard (l'indexation octree COPC est ignoree, seuls les points
|
|
comptent ici).
|
|
2. **Export GeoTIFF cassé** (voir `-RASTERIZE` ci-dessus). **Solution retenue** : les images sont
|
|
generees nous-memes (`python/render_images.py diffmap`) via export ASCII des nuages combles +
|
|
binning `scipy.stats.binned_statistic_2d` sur une grille commune + rendu `matplotlib` (colormap
|
|
`jet`, bleu->rouge façon CloudCompare), sans passer par le rasterizer CloudCompare. Une seule
|
|
image de comparaison par niveau (ceil vs ground/plan constant), pas une carte par nuage —
|
|
remplace l'ancienne approche (une heightmap par nuage) qui ne permettait pas de voir directement
|
|
l'ecart entre les deux surfaces.
|
|
3. **CloudCompare reste une appli Qt, meme en `-SILENT`** : plante avec `QXcbConnection: Could not
|
|
connect to display` sans serveur X. Necessite `xvfb`. On demarre UN Xvfb persistant dans
|
|
`docker/entrypoint.sh` (pas un `xvfb-run` par appel CLI, plus rapide et evite les conflits de
|
|
lockfile entre appels concurrents).
|
|
4. Si un futur besoin necessite E57 : le meme probleme de plugin manquant se posera probablement
|
|
(a verifier — `QE57_IO_PLUGIN` n'apparaissait pas non plus dans `dpkg -L cloudcompare`).
|
|
|
|
**Si CloudCompare est mis a jour dans une future version du paquet Debian et regagne GDAL/LAS**, ces
|
|
contournements resteront fonctionnels sans rien casser (ils n'utilisent jamais les fonctionnalites
|
|
manquantes), mais pourraient etre simplifies.
|
|
|
|
## Pieges d'implementation generaux (au-dela de CloudCompare)
|
|
|
|
- **Chemins `/c/...` (style MSYS/git-bash) vs chemins Windows natifs** : un `python.exe` natif Windows
|
|
ne comprend PAS `/c/Users/...` — seulement `C:/Users/...` ou `C:\Users\...`. `curl`/`cat`/`ls`
|
|
(MSYS) acceptent les deux, mais un process Windows natif lance depuis bash avec un chemin `/c/...`
|
|
en argument echouera silencieusement (`FileNotFoundError`) si ce chemin est passe tel quel a une
|
|
fonction Python (`open(...)`) plutot que d'etre resolu par le shell. Toujours utiliser des chemins
|
|
style `C:/...` quand on passe un chemin en argument a un outil natif Windows depuis bash.
|
|
- **TypeScript + closures + `let` mutable** : `tsc` peut perdre le narrowing d'une variable `let`
|
|
mutee dans une closure asynchrone appelee avant un `return` conditionnel (erreur `Property 'x' does
|
|
not exist on type 'never'`). Contournement : accumuler dans un tableau (`push`) plutot que de muter
|
|
une variable `best` capturee, puis trier/reduire a la fin.
|
|
- **Nest CLI + fichiers de test** : `nest build` doit utiliser `tsconfig.build.json` (qui exclut
|
|
`**/*.spec.ts`) pour ne pas emettre les tests dans `dist/`. Le `tsconfig.json` de base (sans
|
|
exclude) reste utilise par `tsc --noEmit` (script `typecheck`) et par `ts-jest`, pour que les tests
|
|
soient bien type-checkes.
|
|
- **`better-sqlite3`** necessite des outils de build natifs (python3/make/g++) — presents par defaut
|
|
dans l'image `node:20-bookworm` utilisee pour le stage de build, absents volontairement du stage
|
|
final (juste `node_modules` deja compile est copie).
|
|
|
|
## Deploiement (Coolify)
|
|
|
|
- Image construite en deux etapes : build (Node, avec lint+typecheck+test+compilation) puis runtime
|
|
(Debian + CloudCompare + xvfb + Python). Le build Docker **echoue** si lint, typecheck ou tests
|
|
echouent (`RUN npm run lint|typecheck|test` avant `RUN npm run build` dans le Dockerfile) — sert de
|
|
garde-fou avant tout deploiement automatique via webhook.
|
|
- `docker-compose.yml` utilise un **volume nomme** (`app-data:/data`), pas un bind mount vers `./data` :
|
|
Coolify re-clone le repo a chaque deploiement, un bind mount relatif au checkout perdrait les
|
|
donnees (uploads, resultats, base sqlite) a chaque redeploy.
|
|
- **Mais** si la resource Coolify est de type "Application" (build pack Dockerfile, pas "Docker
|
|
Compose"), `docker-compose.yml` est **completement ignore** par Coolify, y compris son `volumes:`.
|
|
Le `VOLUME ["/data"]` du Dockerfile cree alors juste un volume anonyme a chaque nouveau conteneur,
|
|
jamais reattache au precedent -> toutes les donnees disparaissent a chaque redeploy sans aucune
|
|
erreur visible. Il faut explicitement ajouter un volume persistant sur `/data` dans l'onglet
|
|
"Storages" de l'application Coolify. Observe reellement sur ce projet : Coolify auto-detecte par
|
|
defaut un repo avec `package.json` comme un projet Node et choisit le build pack **Nixpacks**
|
|
(qui ignore le `Dockerfile` et tente de build un projet Node generique — echoue forcement ici,
|
|
aucune connaissance de CloudCompare/xvfb/Python). Il faut forcer manuellement le build pack sur
|
|
"Dockerfile" dans les reglages de l'application — et donc, une fois ce changement fait, ne pas
|
|
oublier le volume "Storages" puisqu'on est passe en mode "Application".
|
|
- `HEALTHCHECK` dans le Dockerfile (`curl` sur `/health`, route publique non authentifiee) : Coolify
|
|
l'utilise pour determiner si le deploiement a reussi.
|
|
- Le port d'ecoute est configurable via `PORT` (`config.ts` lit `process.env.PORT`, defaut 3000).
|
|
- Auth HTTP Basic globale (UI + API) via middleware Express (`app.use`, pas un Guard Nest) enregistre
|
|
dans `main.ts` **avant** `app.listen()` — verifie empiriquement que ca protege bien aussi les
|
|
fichiers statiques servis par `ServeStaticModule` (pas seulement les routes `@Controller`), malgre
|
|
le fait que `ServeStaticModule` s'enregistre via le systeme de modules Nest plutot que directement
|
|
sur l'instance `app`. Desactivable si `AUTH_USERNAME`/`AUTH_PASSWORD_HASH` absents (dev local).
|
|
- **Piege `.env` + hash bcrypt** : un hash bcrypt contient des `$` (`$2a$10$eg...`). Docker Compose
|
|
interprete `$` dans un fichier `.env` comme le debut d'une substitution de variable — sans
|
|
echappement, `AUTH_PASSWORD_HASH=$2a$10$eg...` est tronque silencieusement a `$2a$10.` (verifie via
|
|
`docker compose config` : la valeur affichee etait bien tronquee, le reste du hash disparaissait
|
|
sans aucune erreur). **Fix** : dans `.env`, doubler chaque `$` du hash (`$$2a$$10$$eg...`) ;
|
|
`docker compose` les collapse en `$` uniques au runtime (reverifie via `docker exec ...
|
|
printenv AUTH_PASSWORD_HASH` : le conteneur recoit bien le hash original complet, un seul `$`).
|
|
Documente dans `.env.example`. Coolify (qui n'utilise pas de fichier `.env` mais son propre
|
|
formulaire "Environment Variables") n'a probablement pas ce probleme puisqu'il n'y a pas de fichier
|
|
`.env` a parser — a confirmer au premier deploiement avec l'auth active.
|
|
- **Autre piege distinct rencontre en testant** : apres `docker compose up -d` (sans `--build`),
|
|
le conteneur peut tourner sur une **image cachee obsolete** ne contenant pas les derniers
|
|
changements de code (auth absente, etc.) sans aucun avertissement. Toujours utiliser
|
|
`docker compose up -d --build` en local pour etre sur de tester le code courant.
|
|
|
|
## Constantes du pipeline (voir `src/config.ts`)
|
|
|
|
- `decimationFactor = 2`, `decimationSteps = 5` (fixes par la spec, pas exposes en config utilisateur).
|
|
- `gridStepMultiplier = 2` : grid step de `-VOLUME` a CHAQUE niveau = pas spatial de ce niveau x2
|
|
(niveau 0 : `initialStep x2`). Adapte dynamiquement, pas fige — voir section `-VOLUME` plus haut.
|
|
- `statsSampleCap = 50000` : nb de points echantillonnes pour estimer la distance mediane au plus
|
|
proche voisin (KD-tree scipy) qui sert de pas spatial initial.
|
|
- `maxEdgeLengthMultiplier = 3` : distance max d'interpolation Delaunay (remplissage de trous) a
|
|
CHAQUE niveau = gridStep de ce niveau x3. Override utilisateur possible (`maxEdgeLengthOverride`,
|
|
valeur absolue en metres) si ce defaut comble trop/pas assez pour un nuage donne.
|
|
- `minPointsToContinue = 25` : la decimation s'arrete si un niveau (ou l'un des 2 nuages en mode
|
|
compare) tombe en dessous.
|
|
- `matchingCellsWarnThreshold = 90` : seuil d'affichage "a verifier" dans l'UI.
|