use anyhow::Result;
use crate::sdf::{self, Variability};
use crate::usd::{Attribute, Prim, Stage};
use super::impl_shade_schema;
use super::tokens as tok;
use crate::schemas::common::get_typed;
#[derive(Clone, derive_more::Deref)]
pub struct Shader(Prim);
impl Shader {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_SHADER)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_SHADER).map(|o| o.map(Self))
}
pub fn id_attr(&self) -> Attribute {
self.attribute(tok::A_INFO_ID)
}
pub fn create_id_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_INFO_ID, "token")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn id(&self) -> Result<Option<String>> {
self.id_attr().get::<String>()
}
pub fn implementation_source_attr(&self) -> Attribute {
self.attribute(tok::A_INFO_IMPLEMENTATION_SOURCE)
}
pub fn create_implementation_source_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_INFO_IMPLEMENTATION_SOURCE, "token")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn source_asset_attr(&self) -> Attribute {
self.attribute(tok::A_INFO_SOURCE_ASSET)
}
pub fn create_source_asset_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_INFO_SOURCE_ASSET, "asset")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn source_asset_subidentifier_attr(&self) -> Attribute {
self.attribute(tok::A_INFO_SOURCE_ASSET_SUBIDENTIFIER)
}
pub fn create_source_asset_subidentifier_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_INFO_SOURCE_ASSET_SUBIDENTIFIER, "token")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
pub fn source_code_attr(&self) -> Attribute {
self.attribute(tok::A_INFO_SOURCE_CODE)
}
pub fn create_source_code_attr(&self) -> Result<Attribute> {
Ok(self
.create_attribute(tok::A_INFO_SOURCE_CODE, "string")?
.set_custom(false)?
.set_variability(Variability::Uniform)?)
}
}
impl_shade_schema!(connectable Shader);
#[derive(Clone, derive_more::Deref)]
pub struct NodeGraph(Prim);
impl NodeGraph {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_NODE_GRAPH)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_NODE_GRAPH).map(|o| o.map(Self))
}
}
impl_shade_schema!(connectable NodeGraph);
#[derive(Clone, derive_more::Deref)]
pub struct Material(Prim);
impl Material {
pub fn define(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Self> {
Ok(Self(stage.define_prim(path)?.set_type_name(tok::T_MATERIAL)?))
}
pub fn get(stage: &Stage, path: impl Into<sdf::Path>) -> Result<Option<Self>> {
get_typed(stage, path, tok::T_MATERIAL).map(|o| o.map(Self))
}
pub fn surface_output(&self) -> Attribute {
self.attribute(tok::A_OUTPUTS_SURFACE)
}
pub fn create_surface_output(&self) -> Result<Attribute> {
self.create_terminal_output(tok::UNIVERSAL_RENDER_CONTEXT, tok::TERMINAL_SURFACE)
}
pub fn displacement_output(&self) -> Attribute {
self.attribute(tok::A_OUTPUTS_DISPLACEMENT)
}
pub fn create_displacement_output(&self) -> Result<Attribute> {
self.create_terminal_output(tok::UNIVERSAL_RENDER_CONTEXT, tok::TERMINAL_DISPLACEMENT)
}
pub fn volume_output(&self) -> Attribute {
self.attribute(tok::A_OUTPUTS_VOLUME)
}
pub fn create_volume_output(&self) -> Result<Attribute> {
self.create_terminal_output(tok::UNIVERSAL_RENDER_CONTEXT, tok::TERMINAL_VOLUME)
}
pub fn create_surface_output_for(&self, render_context: &str) -> Result<Attribute> {
self.create_terminal_output(render_context, tok::TERMINAL_SURFACE)
}
pub fn compute_surface_source(&self) -> Result<Option<Shader>> {
let mut conns = self.surface_output().connections()?;
if conns.is_empty() {
let suffix = format!(":{}", tok::TERMINAL_SURFACE);
let mut contexts: Vec<String> = self
.stage()
.prim_at(self.path().clone())
.property_names()?
.into_iter()
.filter(|prop| {
prop.strip_prefix(tok::NS_OUTPUTS)
.and_then(|rest| rest.strip_suffix(&suffix))
.is_some_and(|ctx| !ctx.is_empty() && !ctx.contains(':'))
})
.collect();
contexts.sort();
for prop in contexts {
conns = self.attribute(&prop).connections()?;
if !conns.is_empty() {
break;
}
}
}
let Some(source) = conns.into_iter().next() else {
return Ok(None);
};
Shader::get(self.stage(), source.prim_path())
}
fn create_terminal_output(&self, render_context: &str, terminal: &str) -> Result<Attribute> {
let name = if render_context == tok::UNIVERSAL_RENDER_CONTEXT {
format!("outputs:{terminal}")
} else {
format!("outputs:{render_context}:{terminal}")
};
Ok(self.create_attribute(&name, "token")?.set_custom(false)?)
}
}
impl_shade_schema!(connectable Material);
#[cfg(test)]
mod tests {
use super::*;
use crate::schemas::shade::{Connectable, ImplementationSource};
use crate::sdf::Value;
#[test]
fn shader_id_and_inputs() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let shader = Shader::define(&stage, "/Mat/Surface")?;
shader.create_id_attr()?.set("UsdPreviewSurface".to_string())?;
shader
.create_input("diffuseColor", "color3f")?
.set(Value::vec3f(0.8_f32, 0.2, 0.2))?;
shader.create_output("surface", "token")?;
let shader = Shader::get(&stage, "/Mat/Surface")?.expect("Shader");
assert_eq!(shader.id()?.as_deref(), Some("UsdPreviewSurface"));
assert_eq!(
shader.input("diffuseColor").get::<Value>()?,
Some(Value::vec3f(0.8_f32, 0.2, 0.2))
);
assert!(shader
.input_names()
.iter()
.any(|v| v.contains(&"diffuseColor".to_string())));
assert_eq!(
stage.spec_type("/Mat/Surface.outputs:surface")?,
Some(sdf::SpecType::Attribute)
);
Ok(())
}
#[test]
fn shader_source_asset() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let shader = Shader::define(&stage, "/Mat/MdlShader")?;
shader
.create_implementation_source_attr()?
.set(ImplementationSource::SourceAsset)?;
shader
.create_source_asset_attr()?
.set(Value::AssetPath("./OmniPBR.mdl".into()))?;
shader
.create_source_asset_subidentifier_attr()?
.set("OmniPBR".to_string())?;
let shader = Shader::get(&stage, "/Mat/MdlShader")?.expect("Shader");
assert_eq!(
shader.implementation_source_attr().get::<ImplementationSource>()?,
Some(ImplementationSource::SourceAsset)
);
Ok(())
}
#[test]
fn material_surface_terminal() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
Shader::define(&stage, "/Mat/Surface")?
.create_id_attr()?
.set("UsdPreviewSurface".to_string())?;
Shader::get(&stage, "/Mat/Surface")?
.expect("Shader")
.create_output("surface", "token")?;
let shader_out = sdf::path("/Mat/Surface.outputs:surface")?;
let mat = Material::define(&stage, "/Mat")?;
mat.create_surface_output()?.set_connections([shader_out.clone()])?;
let mat = Material::get(&stage, "/Mat")?.expect("Material");
assert_eq!(mat.surface_output().connections()?, vec![shader_out]);
let surface = mat.compute_surface_source()?.expect("surface shader");
assert_eq!(surface.path().as_str(), "/Mat/Surface");
Ok(())
}
#[test]
fn material_render_context_terminal() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let src = sdf::path("/Mat/RiSurface.outputs:surface")?;
Material::define(&stage, "/Mat")?
.create_surface_output_for("ri")?
.set_connections([src])?;
assert_eq!(
stage.spec_type("/Mat.outputs:ri:surface")?,
Some(sdf::SpecType::Attribute)
);
Ok(())
}
#[test]
fn node_graph_interface() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let ng = NodeGraph::define(&stage, "/NG")?;
ng.create_input("gain", "float")?.set(Value::Float(2.0))?;
ng.create_output("out", "color3f")?;
let ng = NodeGraph::get(&stage, "/NG")?.expect("NodeGraph");
assert_eq!(ng.input("gain").get::<f32>()?, Some(2.0));
assert!(ng.output_names().iter().any(|v| v.contains(&"out".to_string())));
Ok(())
}
#[test]
fn connect_connectability_render_type() -> Result<()> {
use crate::schemas::shade::{base_name, Connectability};
let stage = Stage::builder().in_memory("anon.usda")?;
let tex = NodeGraph::define(&stage, "/Mat/Tex")?;
tex.create_output("rgb", "float3")?;
let surf = Shader::define(&stage, "/Mat/Surface")?;
surf.create_input("diffuseColor", "color3f")?
.connect_to(&tex.output("rgb"))?;
assert_eq!(
surf.input("diffuseColor").connections()?,
vec![sdf::path("/Mat/Tex.outputs:rgb")?]
);
assert_eq!(surf.input_connectability("diffuseColor")?, Connectability::Full);
surf.set_input_connectability("diffuseColor", Connectability::InterfaceOnly)?;
assert_eq!(
surf.input_connectability("diffuseColor")?,
Connectability::InterfaceOnly
);
surf.set_input_render_type("diffuseColor", "color")?;
assert_eq!(surf.input_render_type("diffuseColor")?.as_deref(), Some("color"));
tex.set_output_render_type("rgb", "color")?;
assert_eq!(tex.output_render_type("rgb")?.as_deref(), Some("color"));
assert_eq!(base_name("inputs:diffuseColor"), "diffuseColor");
assert_eq!(base_name("outputs:rgb"), "rgb");
Ok(())
}
}