Upload files to "backend/services"
This commit is contained in:
parent
c61c518237
commit
eb5fb040c9
3 changed files with 184 additions and 0 deletions
75
backend/services/converter.py
Normal file
75
backend/services/converter.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import os
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from fastapi import HTTPException
|
||||
from config import EPT_DIR
|
||||
from services.manifest import save_manifest
|
||||
from utils.disk import get_entwine_path
|
||||
|
||||
ENTWINE_PATH = get_entwine_path()
|
||||
ENTWINE_AVAILABLE = ENTWINE_PATH is not None
|
||||
|
||||
def run_entwine(input_path: Path, out_dir: Path) -> dict:
|
||||
if not ENTWINE_AVAILABLE:
|
||||
raise HTTPException(status_code=500,
|
||||
detail="entwine n'est pas installé ou introuvable dans le PATH")
|
||||
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# entwine build -i <input> -o <output_dir>
|
||||
cmd = [
|
||||
ENTWINE_PATH, "build",
|
||||
"-i", str(input_path.absolute()),
|
||||
"-o", str(out_dir.absolute()),
|
||||
]
|
||||
|
||||
print(f"Exécution: {' '.join(cmd)}")
|
||||
print(f"CMD: {cmd}")
|
||||
|
||||
proc = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
check=False,
|
||||
timeout=7200,
|
||||
env=os.environ.copy()
|
||||
)
|
||||
|
||||
if proc.returncode != 0:
|
||||
raise HTTPException(status_code=500,
|
||||
detail=f"entwine failed (code {proc.returncode}):\n{proc.stdout}")
|
||||
|
||||
result = _analyze_ept_output(out_dir, proc.stdout)
|
||||
save_manifest(out_dir, result)
|
||||
return result
|
||||
|
||||
|
||||
def _analyze_ept_output(out_dir: Path, entwine_output: str) -> dict:
|
||||
"""
|
||||
Entwine produit un dossier EPT dont la structure est :
|
||||
out_dir/
|
||||
ept.json ← fichier d'entrée principal
|
||||
ept-data/ ← tuiles binaires
|
||||
ept-hierarchy/ ← hiérarchie des noeuds
|
||||
"""
|
||||
ept_json = out_dir / "ept.json"
|
||||
|
||||
result = {
|
||||
"format": "ept",
|
||||
"entry_file": None,
|
||||
"entry_type": None,
|
||||
"stdout": entwine_output[-2000:],
|
||||
}
|
||||
|
||||
if ept_json.exists():
|
||||
result["entry_file"] = ept_json.relative_to(EPT_DIR).as_posix()
|
||||
result["entry_type"] = "ept.json"
|
||||
# Le dossier EPT = le dossier contenant ept.json
|
||||
result["ept_dir"] = str(ept_json.parent.relative_to(EPT_DIR))
|
||||
else:
|
||||
raise HTTPException(status_code=500,
|
||||
detail=f"entwine a terminé mais ept.json introuvable dans {out_dir}")
|
||||
|
||||
return result
|
||||
85
backend/services/html_generator.py
Normal file
85
backend/services/html_generator.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from config import EPT_DIR
|
||||
|
||||
def generate_viewer_html(pc_id: str, ept_dir: Optional[str],
|
||||
embed: bool = False) -> str:
|
||||
# Fallback : cherche ept.json si le manifest est absent
|
||||
if not ept_dir:
|
||||
out_dir = EPT_DIR / pc_id
|
||||
ept_json = out_dir / "ept.json"
|
||||
if ept_json.exists():
|
||||
ept_dir = pc_id
|
||||
else:
|
||||
return "<h3>Erreur : ept.json introuvable pour cet ID</h3>"
|
||||
|
||||
height_style = "100vh" if embed else "800px"
|
||||
base_url = "/static/potree"
|
||||
|
||||
# L'URL vers ept.json servi via le montage statique /ept_data
|
||||
ept_json_url = f"/ept_data/{ept_dir}/ept.json"
|
||||
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>EPT Viewer - {pc_id}</title>
|
||||
<link rel="stylesheet" href="{base_url}/build/potree/potree.css">
|
||||
<link rel="stylesheet" href="{base_url}/libs/jquery-ui/jquery-ui.min.css">
|
||||
<link rel="stylesheet" href="{base_url}/libs/spectrum/spectrum.css">
|
||||
<link rel="stylesheet" href="{base_url}/libs/jstree/themes/mixed/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="{base_url}/libs/jquery/jquery-3.1.1.min.js"></script>
|
||||
<script src="{base_url}/libs/spectrum/spectrum.js"></script>
|
||||
<script src="{base_url}/libs/jquery-ui/jquery-ui.min.js"></script>
|
||||
<script src="{base_url}/libs/other/BinaryHeap.js"></script>
|
||||
<script src="{base_url}/libs/tween/tween.min.js"></script>
|
||||
<script src="{base_url}/libs/d3/d3.js"></script>
|
||||
<script src="{base_url}/libs/proj4/proj4.js"></script>
|
||||
<script src="{base_url}/libs/openlayers3/ol.js"></script>
|
||||
<script src="{base_url}/libs/i18next/i18next.js"></script>
|
||||
<script src="{base_url}/libs/jstree/jstree.js"></script>
|
||||
<script src="{base_url}/libs/copc/index.js"></script>
|
||||
<script src="{base_url}/build/potree/potree.js"></script>
|
||||
<script src="{base_url}/libs/plasio/js/laslaz.js"></script>
|
||||
|
||||
<div class="potree_container" style="position:absolute;width:100%;height:{height_style};left:0;top:0;">
|
||||
<div id="potree_render_area"></div>
|
||||
<div id="potree_sidebar_container"></div>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
window.viewer = new Potree.Viewer(document.getElementById("potree_render_area"));
|
||||
viewer.setEDLEnabled(true);
|
||||
viewer.setFOV(60);
|
||||
viewer.setPointBudget(1_000_000);
|
||||
viewer.loadSettingsFromURL();
|
||||
viewer.setBackground("skybox");
|
||||
viewer.setDescription("EPT - {pc_id}");
|
||||
|
||||
viewer.loadGUI(() => {{
|
||||
viewer.setLanguage('en');
|
||||
$("#menu_tools").next().show();
|
||||
viewer.toggleSidebar();
|
||||
}});
|
||||
|
||||
// Potree 2.x charge EPT directement via l'URL de ept.json
|
||||
const eptUrl = "{ept_json_url}";
|
||||
console.log("Chargement EPT depuis:", eptUrl);
|
||||
|
||||
Potree.loadPointCloud(eptUrl, "{pc_id}", e => {{
|
||||
let pointcloud = e.pointcloud;
|
||||
let material = pointcloud.material;
|
||||
material.size = 1;
|
||||
material.pointSizeType = Potree.PointSizeType.ADAPTIVE;
|
||||
material.shape = Potree.PointShape.SQUARE;
|
||||
|
||||
viewer.scene.addPointCloud(pointcloud);
|
||||
viewer.fitToScreen();
|
||||
console.log("EPT chargé avec succès");
|
||||
}});
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
24
backend/services/manifest.py
Normal file
24
backend/services/manifest.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
def save_manifest(out_dir: Path, data: dict):
|
||||
manifest = {
|
||||
"conversion_time": time.time(),
|
||||
"format": data.get("format", "ept"), # était "version"
|
||||
"entry_file": data.get("entry_file"),
|
||||
"entry_type": data.get("entry_type"),
|
||||
"ept_dir": data.get("ept_dir"),
|
||||
}
|
||||
with open(out_dir / "manifest.json", 'w', encoding='utf-8') as f:
|
||||
json.dump(manifest, f, indent=2)
|
||||
|
||||
def read_manifest(out_dir: Path) -> dict:
|
||||
manifest_file = out_dir / "manifest.json"
|
||||
if not manifest_file.exists():
|
||||
return {}
|
||||
try:
|
||||
with open(manifest_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
return {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue