Skip to main content

oxihuman_export/
opensim_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! OpenSim musculoskeletal model export.
6
7#[derive(Debug, Clone)]
8pub struct OpenSimBody {
9    pub name: String,
10    pub mass: f64,
11    pub inertia: [f64; 6],
12    pub com: [f64; 3],
13}
14
15#[derive(Debug, Clone)]
16pub struct OpenSimJoint {
17    pub name: String,
18    pub parent_body: String,
19    pub child_body: String,
20    pub location_in_parent: [f64; 3],
21    pub location_in_child: [f64; 3],
22}
23
24#[derive(Debug, Clone)]
25pub struct OpenSimMuscle {
26    pub name: String,
27    pub origin_body: String,
28    pub insertion_body: String,
29    pub optimal_fiber_length: f64,
30    pub tendon_slack_length: f64,
31    pub max_isometric_force: f64,
32}
33
34#[derive(Debug, Clone)]
35pub struct OpenSimModel {
36    pub name: String,
37    pub bodies: Vec<OpenSimBody>,
38    pub joints: Vec<OpenSimJoint>,
39    pub muscles: Vec<OpenSimMuscle>,
40}
41
42pub fn new_opensim_model(name: &str) -> OpenSimModel {
43    OpenSimModel {
44        name: name.to_string(),
45        bodies: Vec::new(),
46        joints: Vec::new(),
47        muscles: Vec::new(),
48    }
49}
50
51pub fn add_opensim_body(model: &mut OpenSimModel, name: &str, mass: f64) {
52    model.bodies.push(OpenSimBody {
53        name: name.to_string(),
54        mass,
55        inertia: [0.0; 6],
56        com: [0.0; 3],
57    });
58}
59
60pub fn add_opensim_joint(model: &mut OpenSimModel, name: &str, parent: &str, child: &str) {
61    model.joints.push(OpenSimJoint {
62        name: name.to_string(),
63        parent_body: parent.to_string(),
64        child_body: child.to_string(),
65        location_in_parent: [0.0; 3],
66        location_in_child: [0.0; 3],
67    });
68}
69
70pub fn add_opensim_muscle(
71    model: &mut OpenSimModel,
72    name: &str,
73    origin: &str,
74    insertion: &str,
75    opt_fiber_len: f64,
76    tendon_slack: f64,
77    max_force: f64,
78) {
79    model.muscles.push(OpenSimMuscle {
80        name: name.to_string(),
81        origin_body: origin.to_string(),
82        insertion_body: insertion.to_string(),
83        optimal_fiber_length: opt_fiber_len,
84        tendon_slack_length: tendon_slack,
85        max_isometric_force: max_force,
86    });
87}
88
89pub fn render_opensim_xml(model: &OpenSimModel) -> String {
90    let mut s = format!("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<OpenSimDocument Version=\"40000\">\n<Model name=\"{}\">\n", model.name);
91    s.push_str("<BodySet>\n<objects>\n");
92    for b in &model.bodies {
93        s.push_str(&format!(
94            "  <Body name=\"{}\">\n    <mass>{}</mass>\n  </Body>\n",
95            b.name, b.mass
96        ));
97    }
98    s.push_str("</objects></BodySet>\n");
99    s.push_str("<JointSet>\n<objects>\n");
100    for j in &model.joints {
101        s.push_str(&format!("  <PinJoint name=\"{}\">\n    <parent_frame>{}</parent_frame>\n    <child_frame>{}</child_frame>\n  </PinJoint>\n",
102            j.name, j.parent_body, j.child_body));
103    }
104    s.push_str("</objects></JointSet>\n");
105    s.push_str("<ForceSet>\n<objects>\n");
106    for m in &model.muscles {
107        s.push_str(&format!("  <Millard2012EquilibriumMuscle name=\"{}\">\n    <optimal_fiber_length>{}</optimal_fiber_length>\n    <max_isometric_force>{}</max_isometric_force>\n  </Millard2012EquilibriumMuscle>\n",
108            m.name, m.optimal_fiber_length, m.max_isometric_force));
109    }
110    s.push_str("</objects></ForceSet>\n</Model>\n</OpenSimDocument>\n");
111    s
112}
113
114pub fn export_opensim(model: &OpenSimModel) -> Vec<u8> {
115    render_opensim_xml(model).into_bytes()
116}
117pub fn opensim_body_count(model: &OpenSimModel) -> usize {
118    model.bodies.len()
119}
120pub fn opensim_muscle_count(model: &OpenSimModel) -> usize {
121    model.muscles.len()
122}
123pub fn validate_opensim_model(model: &OpenSimModel) -> bool {
124    !model.name.is_empty() && !model.bodies.is_empty()
125}
126pub fn opensim_size_bytes(model: &OpenSimModel) -> usize {
127    render_opensim_xml(model).len()
128}
129
130pub fn default_biped_opensim_model() -> OpenSimModel {
131    let mut m = new_opensim_model("BipedModel");
132    add_opensim_body(&mut m, "pelvis", 10.0);
133    add_opensim_body(&mut m, "femur_r", 8.0);
134    add_opensim_joint(&mut m, "hip_r", "pelvis", "femur_r");
135    add_opensim_muscle(&mut m, "rect_fem_r", "pelvis", "femur_r", 0.08, 0.35, 800.0);
136    m
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_new_opensim_model() {
145        let m = new_opensim_model("Test");
146        assert_eq!(m.name, "Test");
147    }
148
149    #[test]
150    fn test_add_body() {
151        let mut m = new_opensim_model("T");
152        add_opensim_body(&mut m, "pelvis", 10.0);
153        assert_eq!(opensim_body_count(&m), 1);
154    }
155
156    #[test]
157    fn test_render_contains_opensim_tag() {
158        let m = new_opensim_model("T");
159        let s = render_opensim_xml(&m);
160        assert!(s.contains("OpenSimDocument"));
161    }
162
163    #[test]
164    fn test_export_opensim_nonempty() {
165        let m = new_opensim_model("T");
166        assert!(!export_opensim(&m).is_empty());
167    }
168
169    #[test]
170    fn test_validate_opensim_model() {
171        let mut m = new_opensim_model("T");
172        add_opensim_body(&mut m, "b", 1.0);
173        assert!(validate_opensim_model(&m));
174    }
175
176    #[test]
177    fn test_default_biped() {
178        let m = default_biped_opensim_model();
179        assert!(opensim_body_count(&m) >= 2);
180        assert!(opensim_muscle_count(&m) >= 1);
181    }
182
183    #[test]
184    fn test_opensim_size_bytes() {
185        let m = new_opensim_model("T");
186        assert!(opensim_size_bytes(&m) > 0);
187    }
188
189    #[test]
190    fn test_muscle_in_render() {
191        let mut m = new_opensim_model("T");
192        add_opensim_body(&mut m, "b1", 1.0);
193        add_opensim_body(&mut m, "b2", 1.0);
194        add_opensim_muscle(&mut m, "muscle1", "b1", "b2", 0.1, 0.2, 500.0);
195        let s = render_opensim_xml(&m);
196        assert!(s.contains("muscle1"));
197    }
198}