Téléchargement des fichiers de scan
Ce guide explique comment télécharger les sorties de scan traitées depuis la RealityConnect API : nuages de points, maillages et images photosphère. Le flux de travail utilise le flux Client Credentials et le scope download:scan.
Prérequis
Section intitulée « Prérequis »- Une application OAuth configurée pour Client Credentials
- Scopes :
read:basic,read:hierarchy,download:scan - Votre
client_idetclient_secret - Accès au contenu du Site qui contient les bundles que vous souhaitez télécharger
read:twin et read:asset ne sont pas requis pour le téléchargement des fichiers de scan.
Organisation des données de scan
Section intitulée « Organisation des données de scan »Les données de scan traitées se trouvent dans des bundles attachés à un Site. Chaque bundle contient un ou plusieurs composants, chaque composant correspondant à un format de sortie différent :
| Type de composant | Format | Description |
|---|---|---|
OgcPointCloudHlod | OGC 3D Tiles (norme ouverte) | Données de nuage de points. Point d’entrée tileset.json avec des fichiers de tuiles .glb |
RealityMeshHlod | Prevu3D HLOD | Données de maillage. Point d’entrée hlod_tree.json avec des fichiers de tuiles .pvt |
RealityPhotosphere | Images JPEG | Données photosphère. Manifeste stations.json avec des images .jpeg |
Flux de bout en bout
Section intitulée « Flux de bout en bout »flowchart LR A["1. S'authentifier"] --> B["2. Trouver un Site"] B --> C["3. Lister les bundles"] C --> D["4. Obtenir les détails du bundle"] D --> E["5. Télécharger les fichiers d'entrée"] E --> F["6. Suivre les références de fichiers"]
| Étape | Point de terminaison | Ce que vous obtenez |
|---|---|---|
| S’authentifier | POST /oauth/token | Jeton d’accès |
| Résoudre l’URL API | GET /oauth/api-info | apiUrl régionale et ID d’organisation |
| Trouver un Site | GET /v1/nodes/{id}/browse | ID du Site depuis la hiérarchie de contenu |
| Lister les bundles | GET /v1/site/{siteId}/bundles | ID des bundles pour le Site |
| Obtenir les composants | GET /v1/site/{siteId}/bundles/{bundleId} | signedLink et roots par composant |
| Télécharger les fichiers | GET sur l’URL CDN construite | Fichiers d’entrée, tuiles et images |
Étape 1 — S’authentifier
Section intitulée « Étape 1 — S’authentifier »Obtenez un jeton d’accès avec Client Credentials :
POST https://cloud-api.prevu3d.com/oauth/tokenAuthorization: Basic base64(client_id:client_secret)Content-Type: application/x-www-form-urlencoded
grant_type=client_credentialsRésolvez votre URL API régionale :
GET https://cloud-api.prevu3d.com/oauth/api-infoAuthorization: Bearer {access_token}La réponse contient apiUrl (par exemple https://api-ue1.prevu3d.com/realityconnect-api). Utilisez-la comme URL de base pour tous les appels API suivants.
Étape 2 — Trouver un Site
Section intitulée « Étape 2 — Trouver un Site »Parcourez la hiérarchie des nœuds en partant de votre organisation :
GET {api_url}/v1/nodes/{organizationId}/browseAuthorization: Bearer {access_token}Parcourez l’arborescence de manière récursive jusqu’à trouver un nœud avec type: "Site". Notez son id.
Étape 3 — Lister les bundles
Section intitulée « Étape 3 — Lister les bundles »GET {api_url}/v1/site/{siteId}/bundlesAuthorization: Bearer {access_token}Retourne une liste paginée des bundles pour ce Site.
Étape 4 — Obtenir les détails du bundle
Section intitulée « Étape 4 — Obtenir les détails du bundle »GET {api_url}/v1/site/{siteId}/bundles/{bundleId}Authorization: Bearer {access_token}Exemple de réponse :
{ "id": "f75de673-...", "name": "My Bundle", "components": [ { "id": "abc123", "type": "OgcPointCloudHlod", "signedLink": "https://cdn.prevu3d.com/.../OgcPointCloudHlod/*?Policy=...&Signature=...&Key-Pair-Id=...", "roots": [ "sessions/0f4a60cb09a8/tileset.json", "sessions/2a5ed597dc0f/tileset.json" ] }, { "id": "def456", "type": "RealityMeshHlod", "signedLink": "https://cdn.prevu3d.com/.../RealityMeshHlod/*?Policy=...&Signature=...&Key-Pair-Id=...", "roots": ["hlod_tree.json"] }, { "id": "ghi789", "type": "RealityPhotosphere", "signedLink": "https://cdn.prevu3d.com/.../RealityPhotosphere/*?Policy=...&Signature=...&Key-Pair-Id=...", "roots": ["stations.json"] } ]}Chaque composant expose deux champs nécessaires au téléchargement :
signedLink: une URL CDN signée se terminant par/*. Le*est un caractère de remplacement. La signature accorde un accès en lecture à tous les fichiers sous ce préfixe, et pas seulement à un seul fichier.roots: un tableau de chemins relatifs vers le ou les fichiers d’entrée de ce composant.
Étape 5 — Télécharger les fichiers
Section intitulée « Étape 5 — Télécharger les fichiers »Construisez une URL téléchargeable en remplaçant le * dans signedLink par un chemin de fichier :
download_url = component["signedLink"].replace("*", root)response = requests.get(download_url)Les paramètres de requête (Policy, Signature, Key-Pair-Id) restent attachés et authentifient la requête. Téléchargez les octets du fichier directement depuis le CDN sans en-tête Authorization sur le GET.
Détails par type de composant
Section intitulée « Détails par type de composant »OgcPointCloudHlod (nuages de points)
Section intitulée « OgcPointCloudHlod (nuages de points) »Point d’entrée : chaque valeur dans roots est un chemin vers un fichier tileset.json (un par session de scan).
for root in component["roots"]: url = component["signedLink"].replace("*", root) tileset = requests.get(url).json()Parcours des tuiles : tileset.json suit la spécification OGC 3D Tiles. Il contient un arbre de nœuds de tuiles. Chaque nœud peut avoir un champ content avec un uri pointant vers un fichier .glb relatif au répertoire du tileset :
{ "asset": { "version": "1.0" }, "root": { "boundingVolume": { }, "content": { "uri": "cell.glb" }, "children": [ { "content": { "uri": "cell0.glb" }, "children": [ ] } ] }}Résolvez les chemins des tuiles par rapport au répertoire du tileset :
session_dir = root.rsplit("/tileset.json", 1)[0]tile_path = f"{session_dir}/{tile_uri}"tile_url = component["signedLink"].replace("*", tile_path)tile_data = requests.get(tile_url).contentParcourez l’arborescence de manière récursive pour télécharger toutes les tuiles.
RealityMeshHlod (maillages)
Section intitulée « RealityMeshHlod (maillages) »Point d’entrée : roots contient ["hlod_tree.json"].
url = component["signedLink"].replace("*", "hlod_tree.json")hlod_tree = requests.get(url).json()Structure de l’arbre : hlod_tree.json contient un arbre HLOD :
{ "environment_offset": [ ], "version": 1, "root": { "center": [1.17, -1.22, 5.63], "size": [50.54, 46.61, 19.63], "model_path": "cell", "density": 6.73, "children": [ { "model_path": "cell0", "children": [ ] } ], "textures": [ { "name": "color", "min_mip": 5, "max_mip": 11, "path": "cell" }, { "name": "ao", "min_mip": 5, "max_mip": 11, "path": "cell_ao" } ] }}Convention de nommage des tuiles : les fichiers de géométrie et de texture utilisent le modèle {model_path}_{mip_level}.pvt :
model_path = node["model_path"]mip_level = texture["min_mip"]tile_file = f"{model_path}_{mip_level}.pvt"
tile_url = component["signedLink"].replace("*", tile_file)tile_data = requests.get(tile_url).contentParcourez le tableau children de manière récursive pour découvrir toutes les valeurs model_path à chaque niveau de détail.
RealityPhotosphere (photosphères)
Section intitulée « RealityPhotosphere (photosphères) »Point d’entrée : roots contient ["stations.json"].
url = component["signedLink"].replace("*", "stations.json")stations = requests.get(url).json()Fichiers supplémentaires :
index.tsv: un fichier d’index séparé par des tabulations à côté destations.json- Les images photosphère suivent le modèle
station_{n}/H_{face}_{x}_{y}.jpeg
image_path = "station_1/H_0_0_0.jpeg"image_url = component["signedLink"].replace("*", image_path)image_data = requests.get(image_url).contentAnalysez stations.json pour découvrir tous les répertoires de stations et leurs fichiers image associés.
Exemple complet
Section intitulée « Exemple complet »Ce script s’authentifie, trouve un Site, récupère le premier bundle, télécharge les fichiers d’entrée et suit une tuile ou une image référencée par composant.
import base64import jsonimport osfrom pathlib import Path
import requests
CLIENT_ID = "your-client-id"CLIENT_SECRET = "your-client-secret"CLOUD_API_BASE = "https://cloud-api.prevu3d.com"OUTPUT_DIR = Path("downloads")
credentials = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()token_response = requests.post( f"{CLOUD_API_BASE}/oauth/token", data={"grant_type": "client_credentials"}, headers={"Authorization": f"Basic {credentials}"},)token_response.raise_for_status()access_token = token_response.json()["access_token"]headers = {"Authorization": f"Bearer {access_token}"}
api_info = requests.get(f"{CLOUD_API_BASE}/oauth/api-info", headers=headers).json()api_url = api_info["apiUrl"].rstrip("/")organization_id = api_info["organization"]["id"]
def api_get(path: str) -> dict: response = requests.get(f"{api_url}{path}", headers=headers) response.raise_for_status() return response.json()
def download(url: str, dest: Path) -> bool: response = requests.get(url, timeout=30) if not response.ok: return False dest.parent.mkdir(parents=True, exist_ok=True) dest.write_bytes(response.content) return True
def find_tile_uri(node: dict, depth: int = 0): if depth > 5 or not isinstance(node, dict): return None content = node.get("content", {}) uri = content.get("uri") or content.get("url") if isinstance(uri, str) and not uri.startswith("http"): return uri for value in node.values(): if isinstance(value, dict): found = find_tile_uri(value, depth + 1) if found: return found elif isinstance(value, list): for item in value: found = find_tile_uri(item, depth + 1) if found: return found return None
queue = api_get(f"/v1/nodes/{organization_id}/browse").get("items", [])site_id = Nonevisited = set()
while queue and not site_id: node = queue.pop(0) if node["id"] in visited: continue visited.add(node["id"]) browse = api_get(f"/v1/nodes/{node['id']}/browse") current = browse.get("node", {}) if current.get("type", "").lower() == "site": site_id = current["id"] break queue.extend(browse.get("items", []))
if not site_id: raise RuntimeError("No site found in hierarchy")
bundles = api_get(f"/v1/site/{site_id}/bundles")bundle_id = bundles["items"][0]["id"]bundle = api_get(f"/v1/site/{site_id}/bundles/{bundle_id}")
for component in bundle.get("components", []): comp_type = component.get("type", "unknown") signed_link = component.get("signedLink", "") roots = component.get("roots", [])
if not signed_link or not roots: continue
for root in roots: entry_url = signed_link.replace("*", root) entry_dest = OUTPUT_DIR / comp_type / root.replace("/", os.sep) if not download(entry_url, entry_dest): continue
if root.endswith("tileset.json"): tileset = json.loads(entry_dest.read_bytes()) tile_uri = find_tile_uri(tileset.get("root", tileset)) if tile_uri: session_dir = root.rsplit("/tileset.json", 1)[0] tile_path = f"{session_dir}/{tile_uri}" if session_dir else tile_uri download(signed_link.replace("*", tile_path), OUTPUT_DIR / comp_type / tile_path.replace("/", os.sep))
elif root == "hlod_tree.json": tree = json.loads(entry_dest.read_bytes()) model_path = tree.get("root", {}).get("model_path", "") if model_path: tile_file = f"{model_path}_5.pvt" download(signed_link.replace("*", tile_file), OUTPUT_DIR / comp_type / tile_file)
elif root == "stations.json": download(signed_link.replace("*", "index.tsv"), OUTPUT_DIR / comp_type / "index.tsv") download(signed_link.replace("*", "station_1/H_0_0_0.jpeg"), OUTPUT_DIR / comp_type / "station_1" / "H_0_0_0.jpeg")
breakEt ensuite ?
Section intitulée « Et ensuite ? »- Configurez l’authentification de bout en bout dans le guide Flux Client Credentials.
- Téléversez et traitez de nouvelles captures avec les Flux de travail Data Bundle.
- Parcourez chaque opération dans la référence API.