use std::path::{Path, PathBuf};
use openusd::{sdf, tf};
use crate::Builder;
use crate::error::Error;
use crate::load;
use crate::model;
#[derive(Debug)]
pub struct NodeLibrary {
pub name: String,
pub path: PathBuf,
pub layers: Vec<PathBuf>,
pub nodes: Vec<Node>,
}
#[derive(Debug)]
pub struct Node {
pub prim_name: String,
pub id: tf::Token,
pub documentation: Option<String>,
pub inputs: Vec<Port>,
pub outputs: Vec<Port>,
}
#[derive(Debug)]
pub struct Port {
pub base_name: String,
pub type_name: sdf::ValueTypeName,
pub fallback: Option<sdf::Value>,
pub documentation: Option<String>,
pub allowed_tokens: Vec<tf::Token>,
pub interface_only: bool,
}
pub fn read(builder: &Builder, name: &str, path: &Path) -> Result<NodeLibrary, Error> {
let invalid = |cause: String| Error::ShaderDefs {
path: path.to_path_buf(),
cause,
};
let stage = load::open_stage(builder, path)?;
let mut nodes = Vec::new();
for prim in stage.prim(sdf::Path::abs_root())?.children()? {
if prim.type_name()?.as_deref() != Some("Shader") {
continue;
}
let Some(id) = stage.field::<tf::Token>(prim.path().append_property("info:id")?, sdf::FieldKey::Default)?
else {
continue;
};
let prim_name = prim.path().name().unwrap_or_default().to_owned();
let mut inputs = Vec::new();
let mut outputs = Vec::new();
for property in prim.authored_property_names()? {
let (ports, base_name) = if let Some(base) = property.strip_prefix("inputs:") {
(&mut inputs, base)
} else if let Some(base) = property.strip_prefix("outputs:") {
(&mut outputs, base)
} else {
continue;
};
let attr = prim.attribute(property.as_str());
let Some(type_name) = attr.type_name()? else {
return Err(invalid(format!("{prim_name}.{property} declares no type")));
};
ports.push(Port {
base_name: base_name.to_owned(),
type_name,
fallback: stage.field::<sdf::Value>(attr.path(), sdf::FieldKey::Default)?,
documentation: attr.get_metadata::<String>(sdf::FieldKey::Documentation.as_str())?,
allowed_tokens: model::allowed_tokens(
attr.get_metadata::<sdf::Value>(sdf::FieldKey::AllowedTokens.as_str())?
.as_ref(),
),
interface_only: attr.get_metadata::<tf::Token>("connectability")?.as_deref() == Some("interfaceOnly"),
});
}
nodes.push(Node {
documentation: prim.get_metadata::<String>(sdf::FieldKey::Documentation.as_str())?,
prim_name,
id,
inputs,
outputs,
});
}
if nodes.is_empty() {
return Err(invalid("it defines no shader node".to_owned()));
}
if let Some(error) = Error::composition(path, &stage) {
return Err(error);
}
Ok(NodeLibrary {
name: name.to_owned(),
path: path.to_path_buf(),
layers: load::watched_layers(&stage),
nodes,
})
}