BlueGit

golden-age paperdoll-renderer

Files

paperdoll-renderer / examples / production_fixture.rs

production_fixture.rs

production_fixture.rs 5.7 KiB Download Raw
1//! Actual native end-to-end batch smoke; no GUI input automation or preview delays.
2//! Usage: production_fixture ROOT MALE.(glb|fbx) FEMALE.(glb|fbx) male|female WIDTH HEIGHT FPS [equipment] [--height-milli=N] [--center-milli=N]
3use eframe::App;
4use paperdoll_renderer::{
5 editor::EditorApp,
6 equipment, preview,
7 session::{adopt_file, Session, Sex},
8};
9use std::path::PathBuf;
10struct Smoke {
11 editor: EditorApp,
12 root: PathBuf,
13 last: u32,
14 frames: u64,
15 done: bool,
16}
17impl App for Smoke {
18 fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) {
19 self.frames += 1;
20 self.editor.update(ctx, frame);
21 let (completed, total, label) = self.editor.render_progress();
22 if completed != self.last {
23 println!("PASS {completed}/{total}: {label}");
24 self.last = completed;
25 }
26 if !self.editor.render_is_running() && !self.done {
27 self.done = true;
28 let error = self.editor.render_error();
29 let success = error.is_none() && total > 0 && completed == total;
30 let value = serde_json::json!({"success":success,"completed":completed,"total":total,"last_pass":label,"gui_updates":self.frames,"error":error});
31 std::fs::write(
32 self.root.join("smoke-result.json"),
33 serde_json::to_vec_pretty(&value).unwrap(),
34 )
35 .unwrap();
36 for warning in self.editor.render_warnings() {
37 println!("WARNING {warning}");
38 }
39 println!("RESULT {value}");
40 ctx.send_viewport_cmd(eframe::egui::ViewportCommand::Close);
41 }
42 }
43 fn on_exit(&mut self, gl: Option<&glow::Context>) {
44 self.editor.on_exit(gl);
45 }
46}
47fn main() -> Result<(), Box<dyn std::error::Error>> {
48 let a = std::env::args().skip(1).collect::<Vec<_>>();
49 if a.len() < 7 {
50 return Err(
51 "ROOT MALE FEMALE male|female WIDTH HEIGHT FPS [equipment] [--height-milli=N] [--center-milli=N]".into(),
52 );
53 }
54 let root = PathBuf::from(&a[0]);
55 let mut s = Session::new();
56 s.install_new_project(&root)?;
57 for (sex, index) in [(Sex::Male, 1), (Sex::Female, 2)] {
58 let path = adopt_file(&root, &PathBuf::from(&a[index]), "assets/body")?;
59 s.set_body_path(sex, path);
60 }
61 s.set_render(a[4].parse()?, a[5].parse()?, a[6].parse()?);
62 let sex = match a[3].as_str() {
63 "male" => Sex::Male,
64 "female" => Sex::Female,
65 _ => return Err("invalid sex".into()),
66 };
67 let equipment = a.get(7).is_some_and(|v| v == "equipment");
68 if let Some(value) = a.iter().find_map(|v| v.strip_prefix("--height-milli=")) {
69 s.opened.as_mut().unwrap().document.render.framing[usize::from(sex == Sex::Female)]
70 .height_milli = value.parse()?;
71 }
72 if let Some(value) = a.iter().find_map(|v| v.strip_prefix("--center-milli=")) {
73 s.opened.as_mut().unwrap().document.render.framing[usize::from(sex == Sex::Female)]
74 .center_y_milli = value.parse()?;
75 }
76
77 if equipment {
78 let assets = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/equipment");
79 let mut p = equipment::Preset::new("sword".into());
80 p.provenance = "CC0 generated fixture".into();
81 equipment::import_obj(&mut p, &assets.join("sword.obj"))?;
82 equipment::import_shiny(&mut p, 0, &assets.join("shiny.png"))?;
83 for (i, sex) in [(0, Sex::Male), (1, Sex::Female)] {
84 let b = preview::load_body_file(&root.join(s.body_path(sex).unwrap()))?;
85 let joint = b
86 .joint_names
87 .iter()
88 .position(|n| n == "RightHand")
89 .ok_or("RightHand absent")?;
90 p.bind(i, &b, 0, joint)?;
91 }
92 let i = s.add_equipment();
93 s.equipment[i].slug = "sword".into();
94 s.equipment[i].preset = Some(p);
95 }
96 s.save()?;
97 for sex in [Sex::Male, Sex::Female] {
98 let b = preview::load_body_file(&root.join(s.body_path(sex).unwrap()))?;
99 println!(
100 "PLAN {sex:?}: {:?}",
101 paperdoll_renderer::production::plan(&b, a[6].parse()?)?
102 );
103 }
104 let root2 = root.clone();
105 eframe::run_native(
106 "Paperdoll Production Render",
107 eframe::NativeOptions {
108 viewport: eframe::egui::ViewportBuilder::default().with_inner_size([1150., 820.]),
109 ..paperdoll_renderer::preview_gl::native_options()
110 },
111 Box::new(move |_| {
112 let mut editor = EditorApp::default();
113 editor.open_path(root2.join("character.ppd"));
114 if equipment {
115 editor.open_equipment_configuration(0, sex);
116 } else {
117 editor.open_body_configuration(sex);
118 }
119 editor.render_all_angles();
120 Ok(Box::new(Smoke {
121 editor,
122 root: root2,
123 last: 0,
124 frames: 0,
125 done: false,
126 }))
127 }),
128 )?;
129 let report: serde_json::Value =
130 serde_json::from_slice(&std::fs::read(root.join("smoke-result.json"))?)?;
131 if report["success"] != true {
132 return Err("production smoke failed; see smoke-result.json".into());
133 }
134 // Newest run: loose maps and .ppda atlases must agree cell by cell.
135 let mut runs = std::fs::read_dir(root.join("renders"))?
136 .map(|e| e.map(|e| e.path()))
137 .collect::<Result<Vec<_>, _>>()?;
138 runs.retain(|p| p.is_dir());
139 runs.sort();
140 let run = runs.last().ok_or("geen run")?;
141 let atlas = paperdoll_renderer::atlas::cross_check_run(run)?;
142 println!("ATLAS {atlas}");
143 std::fs::write(
144 root.join("atlas-check.json"),
145 serde_json::to_vec_pretty(&atlas)?,
146 )?;
147 Ok(())
148}