Skip to main content

oxihuman_cli/
utils.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Shared utility functions used by multiple CLI commands.
5
6use anyhow::{Context, Result};
7use std::path::Path;
8
9use oxihuman_core::policy::Policy;
10use oxihuman_export::params_json::import_params;
11use oxihuman_mesh::MeshBuffers;
12use oxihuman_morph::params::ParamState;
13use oxihuman_morph::{apply_expression_to_engine, ExpressionPreset};
14
15/// Load parameters from either an inline JSON string or a file path.
16pub fn load_params(src: &str) -> Result<ParamState> {
17    let json_str = if Path::new(src).exists() {
18        std::fs::read_to_string(src)?
19    } else {
20        src.to_string()
21    };
22    let val: serde_json::Value = serde_json::from_str(&json_str)?;
23    import_params(&val)
24}
25
26/// Build mesh using a manual engine pipeline (used when --expression is specified).
27pub fn build_mesh_with_expression(
28    base_path: &Path,
29    targets_dir: Option<&Path>,
30    params: ParamState,
31    policy: Policy,
32    output: &Path,
33    expression_preset: &ExpressionPreset,
34) -> Result<MeshBuffers> {
35    use oxihuman_core::parser::obj::parse_obj;
36    use oxihuman_mesh::normals::compute_normals;
37    use oxihuman_mesh::suit::apply_suit_flag;
38    use oxihuman_morph::HumanEngine;
39
40    let base_src = std::fs::read_to_string(base_path)
41        .with_context(|| format!("reading base OBJ: {}", base_path.display()))?;
42    let base_mesh = parse_obj(&base_src).context("parsing base OBJ")?;
43    let mut engine = HumanEngine::new(base_mesh, policy);
44
45    // Load body morph targets if a directory was provided
46    if let Some(td) = targets_dir {
47        if td.exists() {
48            if let Ok(n) = engine.load_targets_from_dir_auto(td) {
49                eprintln!("OxiHuman: loaded {} body targets", n);
50            }
51        }
52    }
53
54    // Apply expression targets from {targets_dir}/expression/units/caucasian/
55    let expr_dir = targets_dir.map(|td| td.join("expression").join("units").join("caucasian"));
56
57    if let Some(ref ed) = expr_dir {
58        let expr_count = apply_expression_to_engine(&mut engine, expression_preset, ed);
59        if expr_count > 0 {
60            eprintln!(
61                "OxiHuman: applied {} expression target(s) for '{}'",
62                expr_count, expression_preset.name
63            );
64        } else {
65            eprintln!(
66                "OxiHuman: expression preset '{}' (apply manually with --targets)",
67                expression_preset.name
68            );
69        }
70    } else {
71        eprintln!(
72            "OxiHuman: expression preset '{}' (apply manually with --targets)",
73            expression_preset.name
74        );
75    }
76
77    engine.set_params(params);
78    let morph_buffers = engine.build_mesh();
79
80    let mut mesh = MeshBuffers::from_morph(morph_buffers);
81    compute_normals(&mut mesh);
82    apply_suit_flag(&mut mesh);
83
84    oxihuman_export::glb::export_glb(&mesh, output)
85        .with_context(|| format!("exporting GLB to {}", output.display()))?;
86
87    Ok(mesh)
88}
89
90/// Build a MeshBuffers from a base OBJ, optionally applying morph targets.
91pub fn build_mesh_from_base(
92    base: &Path,
93    targets: Option<&Path>,
94    params: ParamState,
95    policy: Policy,
96) -> Result<MeshBuffers> {
97    use oxihuman_core::parser::obj::parse_obj;
98    use oxihuman_mesh::normals::compute_normals;
99    use oxihuman_mesh::suit::apply_suit_flag;
100    use oxihuman_morph::HumanEngine;
101
102    let src = std::fs::read_to_string(base)
103        .with_context(|| format!("reading base OBJ: {}", base.display()))?;
104    let obj = parse_obj(&src).context("parsing base OBJ")?;
105    let mut engine = HumanEngine::new(obj, policy);
106
107    if let Some(td) = targets {
108        if td.exists() {
109            if let Ok(n) = engine.load_targets_from_dir_auto(td) {
110                eprintln!("OxiHuman: loaded {} targets", n);
111            }
112        }
113    }
114
115    engine.set_params(params);
116    let morph_buf = engine.build_mesh();
117    let mut mesh = MeshBuffers::from_morph(morph_buf);
118    compute_normals(&mut mesh);
119    apply_suit_flag(&mut mesh);
120    Ok(mesh)
121}