#![allow(dead_code)]
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
use tokio::fs::File;
use tokio::io::AsyncReadExt;
use super::AuthClient;
use crate::config::Config;
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Photoscene {
#[serde(rename = "photosceneid")]
pub photoscene_id: String,
pub name: Option<String>,
#[serde(rename = "scenetype")]
pub scene_type: Option<String>,
#[serde(rename = "convertformat")]
pub convert_format: Option<String>,
pub status: Option<String>,
pub progress: Option<String>,
#[serde(rename = "progressmsg")]
pub progress_msg: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct CreatePhotosceneResponse {
pub photoscene: Photoscene,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct UploadResponse {
pub files: Option<UploadFiles>,
pub usage: Option<String>,
pub resource: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct UploadFiles {
pub file: Option<Vec<UploadedFile>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UploadedFile {
pub filename: String,
pub fileid: String,
pub filesize: Option<String>,
pub msg: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ProgressResponse {
pub photoscene: PhotosceneProgress,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PhotosceneProgress {
#[serde(rename = "photosceneid")]
pub photoscene_id: String,
pub progress: String,
#[serde(rename = "progressmsg")]
pub progress_msg: Option<String>,
pub status: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ResultResponse {
pub photoscene: PhotosceneResult,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PhotosceneResult {
#[serde(rename = "photosceneid")]
pub photoscene_id: String,
pub progress: String,
#[serde(rename = "progressmsg")]
pub progress_msg: Option<String>,
#[serde(rename = "scenelink")]
pub scene_link: Option<String>,
#[serde(rename = "filesize")]
pub file_size: Option<String>,
}
#[derive(Debug, Clone, Copy)]
pub enum OutputFormat {
Rcm, Rcs, Obj, Fbx, Ortho, }
impl std::fmt::Display for OutputFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OutputFormat::Rcm => write!(f, "rcm"),
OutputFormat::Rcs => write!(f, "rcs"),
OutputFormat::Obj => write!(f, "obj"),
OutputFormat::Fbx => write!(f, "fbx"),
OutputFormat::Ortho => write!(f, "ortho"),
}
}
}
impl OutputFormat {
pub fn all() -> Vec<Self> {
vec![Self::Rcm, Self::Rcs, Self::Obj, Self::Fbx, Self::Ortho]
}
pub fn description(&self) -> &str {
match self {
OutputFormat::Rcm => "Autodesk ReCap format (point cloud)",
OutputFormat::Rcs => "ReCap scan format",
OutputFormat::Obj => "Wavefront OBJ (mesh)",
OutputFormat::Fbx => "Autodesk FBX (mesh)",
OutputFormat::Ortho => "Orthophoto (2D image)",
}
}
}
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum SceneType {
Aerial,
Object,
}
impl std::fmt::Display for SceneType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SceneType::Aerial => write!(f, "aerial"),
SceneType::Object => write!(f, "object"),
}
}
}
pub struct RealityCaptureClient {
config: Config,
auth: AuthClient,
http_client: reqwest::Client,
}
impl RealityCaptureClient {
pub fn new(config: Config, auth: AuthClient) -> Self {
Self::new_with_http_config(config, auth, crate::http::HttpClientConfig::default())
}
pub fn new_with_http_config(
config: Config,
auth: AuthClient,
http_config: crate::http::HttpClientConfig,
) -> Self {
let http_client = http_config
.create_client()
.unwrap_or_else(|_| reqwest::Client::new());
Self {
config,
auth,
http_client,
}
}
pub async fn create_photoscene(
&self,
name: &str,
scene_type: SceneType,
format: OutputFormat,
) -> Result<Photoscene> {
let token = self.auth.get_token().await?;
let url = format!("{}/photoscene", self.config.reality_capture_url());
let params = [
("scenename", name),
("scenetype", &scene_type.to_string()),
("format", &format.to_string()),
];
let response = self
.http_client
.post(&url)
.bearer_auth(&token)
.form(¶ms)
.send()
.await
.context("Failed to create photoscene")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to create photoscene ({}): {}", status, error_text);
}
let create_response: CreatePhotosceneResponse = response
.json()
.await
.context("Failed to parse photoscene response")?;
Ok(create_response.photoscene)
}
pub async fn upload_photos(
&self,
photoscene_id: &str,
photo_paths: &[&Path],
) -> Result<Vec<UploadedFile>> {
let token = self.auth.get_token().await?;
let url = format!("{}/file", self.config.reality_capture_url());
let mut form = reqwest::multipart::Form::new()
.text("photosceneid", photoscene_id.to_string())
.text("type", "image");
for (i, path) in photo_paths.iter().enumerate() {
let mut file = File::open(path)
.await
.context(format!("Failed to open file: {}", path.display()))?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)
.await
.context("Failed to read file")?;
let filename = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("photo.jpg")
.to_string();
let part = reqwest::multipart::Part::bytes(buffer)
.file_name(filename.clone())
.mime_str("image/jpeg")?;
form = form.part(format!("file[{}]", i), part);
}
let response = self
.http_client
.post(&url)
.bearer_auth(&token)
.multipart(form)
.send()
.await
.context("Failed to upload photos")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to upload photos ({}): {}", status, error_text);
}
let upload_response: UploadResponse = response
.json()
.await
.context("Failed to parse upload response")?;
let files = upload_response
.files
.and_then(|f| f.file)
.unwrap_or_default();
Ok(files)
}
pub async fn start_processing(&self, photoscene_id: &str) -> Result<()> {
let token = self.auth.get_token().await?;
let url = format!(
"{}/photoscene/{}",
self.config.reality_capture_url(),
photoscene_id
);
let response = self
.http_client
.post(&url)
.bearer_auth(&token)
.send()
.await
.context("Failed to start processing")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to start processing ({}): {}", status, error_text);
}
Ok(())
}
pub async fn get_progress(&self, photoscene_id: &str) -> Result<PhotosceneProgress> {
let token = self.auth.get_token().await?;
let url = format!(
"{}/photoscene/{}/progress",
self.config.reality_capture_url(),
photoscene_id
);
let response = self
.http_client
.get(&url)
.bearer_auth(&token)
.send()
.await
.context("Failed to get progress")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to get progress ({}): {}", status, error_text);
}
let progress_response: ProgressResponse = response
.json()
.await
.context("Failed to parse progress response")?;
Ok(progress_response.photoscene)
}
pub async fn get_result(
&self,
photoscene_id: &str,
format: OutputFormat,
) -> Result<PhotosceneResult> {
let token = self.auth.get_token().await?;
let url = format!(
"{}/photoscene/{}?format={}",
self.config.reality_capture_url(),
photoscene_id,
format
);
let response = self
.http_client
.get(&url)
.bearer_auth(&token)
.send()
.await
.context("Failed to get result")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to get result ({}): {}", status, error_text);
}
let result_response: ResultResponse = response
.json()
.await
.context("Failed to parse result response")?;
Ok(result_response.photoscene)
}
pub async fn delete_photoscene(&self, photoscene_id: &str) -> Result<()> {
let token = self.auth.get_token().await?;
let url = format!(
"{}/photoscene/{}",
self.config.reality_capture_url(),
photoscene_id
);
let response = self
.http_client
.delete(&url)
.bearer_auth(&token)
.send()
.await
.context("Failed to delete photoscene")?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
anyhow::bail!("Failed to delete photoscene ({}): {}", status, error_text);
}
Ok(())
}
pub fn available_formats(&self) -> Vec<OutputFormat> {
OutputFormat::all()
}
}