//! Honest capability report for the current editor. //! //! Attachment authoring and target-map production batches are present. //! The rendered 2D lighting/compositor remains not built. /// Product capability named by the design. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Capability { ProjectShell, ObjMtlTextures, Fbx, GltfGlb, SkeletalAnimation, AttachmentEditor, PaperdollRender, Preview2d, } /// Whether this build actually contains the capability. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Availability { Present, NotBuilt, } /// What the process should do with the command line it received. #[derive(Debug, Clone, PartialEq, Eq)] pub enum CliRequest { OpenWindow, InstallDesktop, OpenProject(String), PrintStatus, PrintHelp, PreviewFixtures, } /// Rejected command line. A model path is rejected on purpose: accepting one /// would look like import support. #[derive(Debug, Clone, PartialEq, Eq)] pub enum CliError { UnknownArgument(String), Usage(String), } const CAPABILITIES: [Capability; 8] = [ Capability::ProjectShell, Capability::ObjMtlTextures, Capability::Fbx, Capability::GltfGlb, Capability::SkeletalAnimation, Capability::AttachmentEditor, Capability::PaperdollRender, Capability::Preview2d, ]; pub fn capabilities() -> &'static [Capability] { &CAPABILITIES } pub fn availability(capability: Capability) -> Availability { match capability { Capability::ProjectShell | Capability::ObjMtlTextures | Capability::GltfGlb | Capability::Fbx | Capability::SkeletalAnimation | Capability::AttachmentEditor | Capability::PaperdollRender => Availability::Present, Capability::Preview2d => Availability::NotBuilt, } } pub fn machine_key(capability: Capability) -> &'static str { match capability { Capability::ProjectShell => "project_shell", Capability::ObjMtlTextures => "obj_mtl_textures", Capability::Fbx => "fbx", Capability::GltfGlb => "gltf_glb", Capability::SkeletalAnimation => "skeletal_animation", Capability::AttachmentEditor => "attachment_editor", Capability::PaperdollRender => "paperdoll_render", Capability::Preview2d => "preview_2d", } } pub fn machine_state(availability: Availability) -> &'static str { match availability { Availability::Present => "present", Availability::NotBuilt => "not_built", } } pub fn dutch_label(capability: Capability) -> &'static str { match capability { Capability::ProjectShell => "projectschil", Capability::ObjMtlTextures => "OBJ + MTL + textures", Capability::Fbx => "FBX", Capability::GltfGlb => "glTF/GLB", Capability::SkeletalAnimation => "skeletanimatie", Capability::AttachmentEditor => "3D-attachmenteditor", Capability::PaperdollRender => "paperdoll-render", Capability::Preview2d => "2D-preview", } } pub fn dutch_state(availability: Availability) -> &'static str { match availability { Availability::Present => "aanwezig", Availability::NotBuilt => "niet gebouwd", } } pub fn dutch_summary() -> &'static str { "Deze build leest glTF/GLB, FBX en OBJ+MTL, opent een .ppd-project en toont een live geskinde 3D-preview van glTF/GLB met walk en attack, rigide bone-gebonden OBJ-equipment en binaire .ppe-presets. Render maakt alle clips × frames × 36 hoeken met vaste orthografische camera en diffuse/depth/normal/specular op doelresolutie. De 2D-lichtcompositor is niet gebouwd." } /// Stable status text shared by `--status` and the window. pub fn report() -> String { let mut lines = Vec::with_capacity(2 + CAPABILITIES.len()); lines.push("name: Paperdoll Renderer".to_string()); lines.push("phase: project-editor".to_string()); for capability in CAPABILITIES { lines.push(format!( "{}: {}", machine_key(capability), machine_state(availability(capability)) )); } lines.join("\n") } /// Shared libraries the X11 window loads. `ldd` does not list them: eframe /// opens them at runtime. A missing one panics inside that loader, so the /// shell checks first. pub const LINUX_WINDOW_LIBRARIES: &[&str] = &[ "libxkbcommon-x11.so.0", "libxkbcommon.so.0", "libxcb.so.1", "libX11.so.6", "libGL.so.1", ]; pub fn missing_library_message(missing: &[&str]) -> String { format!( "het venster kan niet openen; deze bibliotheken ontbreken: {}.", missing.join(", ") ) } #[cfg(target_os = "linux")] pub fn missing_window_libraries() -> Vec<&'static str> { LINUX_WINDOW_LIBRARIES .iter() .copied() .filter(|name| { // Safety: `name` is a nul-free soname literal. A missing library // returns Err; no symbol from the library is called. unsafe { libloading::Library::new(name).is_err() } }) .collect() } pub fn help_text() -> &'static str { "\ Paperdoll Renderer Gebruik: paperdoll-renderer startscherm paperdoll-renderer pad/naar/character.ppd open dat project paperdoll-renderer --install-desktop registreer Openen met op Linux paperdoll-renderer --status print de status en sluit paperdoll-renderer --help toon deze tekst paperdoll-renderer --preview-fixture open een voorbeeldproject op het mannelijke lichaam --status staat alleen. Een .ppd-pad of een projectmap opent character.ppd." } pub fn parse_cli<'a, I>(args: I) -> Result where I: IntoIterator, { let args: Vec<&str> = args.into_iter().collect(); if args == ["--install-desktop"] { return Ok(CliRequest::InstallDesktop); } let mut help = false; let mut status = false; let mut preview = false; let mut project = None; for arg in args { match arg { "--help" | "-h" => help = true, "--status" => status = true, "--preview-fixture" => preview = true, other if other.starts_with('-') => { return Err(CliError::UnknownArgument(other.to_string())); } other => { if project.is_some() { return Err(CliError::Usage("geef één projectpad".to_string())); } project = Some(other.to_string()); } } } if help { return Ok(CliRequest::PrintHelp); } if status && (preview || project.is_some()) { return Err(CliError::Usage( "--status staat alleen en neemt geen projectpad aan".to_string(), )); } if status { return Ok(CliRequest::PrintStatus); } if preview && project.is_some() { return Err(CliError::Usage( "--preview-fixture opent het voorbeeld en neemt geen projectpad aan".to_string(), )); } if preview { return Ok(CliRequest::PreviewFixtures); } if let Some(path) = project { return Ok(CliRequest::OpenProject(path)); } Ok(CliRequest::OpenWindow) } #[cfg(test)] mod tests { use super::*; #[test] fn import_and_production_are_present_but_compositor_is_not() { assert_eq!( availability(Capability::ProjectShell), Availability::Present ); assert_eq!(availability(Capability::GltfGlb), Availability::Present); assert_eq!( availability(Capability::ObjMtlTextures), Availability::Present ); assert_eq!(availability(Capability::Fbx), Availability::Present); assert_eq!( availability(Capability::SkeletalAnimation), Availability::Present ); assert_eq!( availability(Capability::AttachmentEditor), Availability::Present ); assert_eq!( availability(Capability::PaperdollRender), Availability::Present ); { let capability = Capability::Preview2d; assert_eq!( availability(capability), Availability::NotBuilt, "{capability:?} must stay not built" ); } } #[test] fn status_report_matches_current_production_without_claiming_compositor() { let report = report(); assert!(report.starts_with("name: Paperdoll Renderer\nphase: project-editor\n")); for key in [ "project_shell", "obj_mtl_textures", "gltf_glb", "fbx", "skeletal_animation", "attachment_editor", "paperdoll_render", ] { assert!( report.contains(&format!("{key}: present")), "missing present {key} in {report}" ); } { let key = "preview_2d"; assert!( report.contains(&format!("{key}: not_built")), "missing {key} in {report}" ); assert!( !report.contains(&format!("{key}: present")), "{key} must not be reported as present" ); } let summary = dutch_summary(); assert!(summary.contains("3D-preview")); assert!(summary.contains("glTF/GLB")); assert!(summary.contains("walk")); } #[test] fn cli_opens_a_window_or_prints_status_and_rejects_model_paths() { assert_eq!(parse_cli(std::iter::empty()), Ok(CliRequest::OpenWindow)); assert_eq!(parse_cli(["--status"]), Ok(CliRequest::PrintStatus)); assert_eq!(parse_cli(["--help"]), Ok(CliRequest::PrintHelp)); assert_eq!(parse_cli(["-h", "--status"]), Ok(CliRequest::PrintHelp)); assert_eq!( parse_cli(["mijn project/character.ppd"]), Ok(CliRequest::OpenProject( "mijn project/character.ppd".to_string() )) ); assert_eq!( parse_cli(["--status", "character.ppd"]), Err(CliError::Usage( "--status staat alleen en neemt geen projectpad aan".to_string() )) ); assert_eq!( parse_cli(["--status", "--render"]), Err(CliError::UnknownArgument("--render".to_string())) ); assert!(help_text().contains("--preview-fixture")); assert_eq!( parse_cli(["--preview-fixture"]), Ok(CliRequest::PreviewFixtures) ); } #[test] fn missing_library_message_names_the_library_and_does_not_claim_import() { let message = missing_library_message(&["libxkbcommon-x11.so.0"]); assert!(message.contains("libxkbcommon-x11.so.0")); assert!(!message.to_lowercase().contains("import")); assert!(!message.to_lowercase().contains("niet gebouwd")); assert!(!LINUX_WINDOW_LIBRARIES.is_empty()); } }