hgame 0.26.4

CG production management structs, e.g. of assets, personnels, progress, etc.
Documentation
use super::*;
use serde_json::{json, Value};
use std::process::Child;

#[derive(Debug, Default)]
/// Handle to a spawned child process, as well as the command
/// and the env vars used in spawning the process.
pub struct LaunchProcess {
    /// Spawned child process.
    pub process: Option<Child>,
    /// Constructed command with all arguments used.
    pub command: Option<Vec<String>>,
    pub envs: Option<HashMap<String, String>>,
}

impl LaunchProcess {
    pub fn process(child: Child) -> Self {
        Self {
            process: Some(child),
            ..Default::default()
        }
    }

    pub fn command(mut self, arg: String) -> Self {
        match self.command.as_mut() {
            Some(args) => {
                args.push(arg);
            }
            None => {
                self.command = Some(vec![arg]);
            }
        };
        self
    }

    pub fn envs(mut self, envs: HashMap<String, String>) -> Self {
        self.envs = Some(envs);
        self
    }

    fn command_as_str(&self) -> String {
        if let Some(cmd) = &self.command {
            cmd.join(" ")
        } else {
            String::from("None")
        }
    }

    fn envs_as_str(&self) -> String {
        if let Some(vars) = &self.envs {
            format!("{:?}", vars)
        } else {
            String::from("None")
        }
    }

    pub fn debug(&self) -> String {
        format!(
            "Command: {}\nEnvs: {}",
            self.command_as_str(),
            self.envs_as_str()
        )
    }
}

pub trait DccEngineEnv: fmt::Debug + Send + Sync {
    fn launch_vanilla_maya(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_custom_maya(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_3ds_max(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_blender(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_zbrush(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_vanilla_mari(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_custom_mari(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_substance_painter(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_substance_designer(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_unity(&self, project: &Project) -> AnyResult<LaunchProcess>;

    fn launch_unreal(&self, project: &Project) -> AnyResult<LaunchProcess>;
}

#[allow(dead_code)]
#[derive(Deserialize, Debug, Default)]
/// The response from Embark Studios' SkyHook server,
/// from JSON deserialized into Rust struct.

pub struct SkyHookResponse {
    #[serde(rename = "ReturnValue")]
    return_value: Vec<String>,

    #[serde(rename = "Success")]
    success: bool,
}

#[derive(Serialize, Default)]
/// The payload sent to Embark Studios' SkyHook server,
/// from Rust struct serialized into JSON.
pub struct SkyHookPayload {
    #[serde(rename = "FunctionName")]
    function_name: String,

    #[serde(rename = "Parameters")]
    param: Value,
}

impl SkyHookPayload {
    pub fn ozone_script(num: u8, assets: HashSet<ProductionAsset>) -> AnyResult<Self> {
        Ok(Self {
            function_name: format!("ozone_script_{}", num),
            ..Default::default()
        }
        .selected_assets(assets)?)
    }

    /// Inserts `"selected_assets"` into `Self::param`.
    pub fn selected_assets(mut self, assets: HashSet<ProductionAsset>) -> AnyResult<Self> {
        self.param = json!({ "selected_assets": AssetRequestBuilder::new(assets).finish()? });
        Ok(self)
    }

    /// For simple `Self::param` of pairs of strings.
    pub fn with_str_params(mut self, param: &[(&str, &str)]) -> Self {
        self.param = param
            .iter()
            .map(|(k, v)| (String::from(*k), String::from(*v)))
            .collect();
        self
    }

    /// From `.dcc_engine/common/skyhook_py2/skyhook/modules/core.py`
    pub fn echo_message(msg: &str) -> Self {
        Self {
            function_name: "echo_message".into(),
            ..Default::default()
        }
        .with_str_params(&[("message", msg)])
    }

    /// From `.dcc_engine/maya/rusty_hunter/scripts/rusty_hunter/mayahii/rh_scene.py` module.
    pub fn open_maya_scene(path: &Path) -> Self {
        Self {
            function_name: "open_scene".into(),
            ..Default::default()
        }
        .with_str_params(&[("path", &path.to_string_lossy().to_string())])
    }
}

/// An "asset sanitizer" before they getting sent inside HTTP requests.
struct AssetRequestBuilder {
    input: Vec<ProductionAsset>,
    output: Vec<StandardAsset>,
}

impl AssetRequestBuilder {
    fn new(assets: HashSet<ProductionAsset>) -> Self {
        Self {
            input: assets.into_iter().collect(),
            output: vec![],
        }
    }

    /// Tries to remove the enum variants by getting to the "inner"s. Fails if encountering
    /// any [`ProductionAsset`] that cannot be made into a [`StandardAsset`].
    fn standard_assets(mut self) -> AnyResult<Self> {
        let mut output = vec![];
        // `Self::input` will be empty after this loop
        while let Some(a) = self.input.pop() {
            output.push(
                a.inner_standard_owned()
                    .context("Unsupported inner type of ProductionAsset")?,
            );
        }
        self.output = output;
        Ok(self)
    }

    fn finish(mut self) -> AnyResult<Vec<StandardAsset>> {
        self = self.standard_assets()?;
        Ok(self.output)
    }
}