use crate::ffi::raw as ffi;
use crate::ffi::raw::{ChoiceListType, ParmType};
use crate::node::ManagerType;
use crate::{
errors::Result, ffi::ParmChoiceInfo, ffi::ParmInfo, node::HoudiniNode, session::Session,
HapiError,
};
use log::debug;
use std::ffi::CString;
use std::path::PathBuf;
struct AssetParmValues {
int: Vec<i32>,
float: Vec<f32>,
string: Vec<String>,
menus: Vec<ParmChoiceInfo>,
}
pub struct AssetParameters {
infos: Vec<ParmInfo>,
values: AssetParmValues,
}
impl<'a> IntoIterator for &'a AssetParameters {
type Item = AssetParm<'a>;
type IntoIter = AssetParmIter<'a>;
fn into_iter(self) -> Self::IntoIter {
AssetParmIter {
iter: self.infos.iter(),
values: &self.values,
}
}
}
impl AssetParameters {
pub fn find_parameter(&self, name: &str) -> Option<AssetParm<'_>> {
self.infos
.iter()
.find(|p| p.name().unwrap() == name)
.map(|info| AssetParm {
info,
values: &self.values,
})
}
}
pub struct AssetParmIter<'a> {
iter: std::slice::Iter<'a, ParmInfo>,
values: &'a AssetParmValues,
}
impl<'a> Iterator for AssetParmIter<'a> {
type Item = AssetParm<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|info| AssetParm {
info,
values: self.values,
})
}
}
pub struct AssetParm<'a> {
info: &'a ParmInfo,
values: &'a AssetParmValues,
}
impl<'a> std::ops::Deref for AssetParm<'a> {
type Target = ParmInfo;
fn deref(&self) -> &Self::Target {
self.info
}
}
#[derive(Debug)]
pub enum ParmValue<'a> {
Int(&'a [i32]),
Float(&'a [f32]),
String(&'a [String]),
Toggle(bool),
NoDefault,
}
impl<'a> AssetParm<'a> {
pub fn default_value(&self) -> ParmValue<'a> {
let size = self.info.size() as usize;
use ParmType::*;
match self.info.parm_type() {
Int | Button => {
let start = self.info.int_values_index() as usize;
ParmValue::Int(&self.values.int[start..start + size])
}
Toggle => {
let start = self.info.int_values_index() as usize;
ParmValue::Toggle(self.values.int[start] == 1)
}
Float | Color => {
let start = self.info.float_values_index() as usize;
ParmValue::Float(&self.values.float[start..start + size])
}
String | PathFileGeo | PathFile | PathFileImage | PathFileDir | Node => {
let start = self.info.string_values_index() as usize;
ParmValue::String(&self.values.string[start..start + size])
}
_ => ParmValue::NoDefault,
}
}
pub fn menu_items(&self) -> Option<&[ParmChoiceInfo]> {
if let ChoiceListType::None = self.choice_list_type() {
return None;
}
let count = self.info.choice_count() as usize;
let start = self.info.choice_index() as usize;
Some(&self.values.menus[start..start + count])
}
}
#[derive(Debug, Clone)]
pub struct AssetLibrary {
pub(crate) lib_id: ffi::HAPI_AssetLibraryId,
pub(crate) session: Session,
pub file: Option<PathBuf>,
}
impl AssetLibrary {
pub fn from_file(session: Session, file: impl AsRef<std::path::Path>) -> Result<AssetLibrary> {
let file = file.as_ref().to_path_buf();
debug!("Loading library file: {:?}", file);
debug_assert!(session.is_valid());
let cs = CString::new(file.as_os_str().to_string_lossy().to_string())?;
let lib_id = crate::ffi::load_library_from_file(&cs, &session, true)?;
Ok(AssetLibrary {
lib_id,
session,
file: Some(file),
})
}
pub fn from_memory(session: Session, data: &[u8]) -> Result<AssetLibrary> {
debug!("Loading library from memory");
debug_assert!(session.is_valid());
let data: &[i8] = unsafe { std::mem::transmute(data) };
let lib_id = crate::ffi::load_library_from_memory(&session, data, true)?;
Ok(AssetLibrary {
lib_id,
session,
file: None,
})
}
pub fn get_asset_count(&self) -> Result<i32> {
debug_assert!(self.session.is_valid());
crate::ffi::get_asset_count(self.lib_id, &self.session)
}
pub fn get_asset_names(&self) -> Result<Vec<String>> {
debug_assert!(self.session.is_valid());
debug!("Retrieving asset names from: {:?}", self.file);
let num_assets = self.get_asset_count()?;
crate::ffi::get_asset_names(self.lib_id, num_assets, &self.session)
.map(|a| a.into_iter().collect())
}
pub fn get_first_name(&self) -> Result<Option<String>> {
debug_assert!(self.session.is_valid());
self.get_asset_names().map(|names| names.first().cloned())
}
pub fn create_asset_for_node<T: AsRef<str>>(
&self,
name: T,
label: Option<T>,
) -> Result<HoudiniNode> {
debug!("Trying to create a node for operator: {}", name.as_ref());
let Some((context, operator)) = name.as_ref().split_once('/') else {
return Err(HapiError::internal("Node name must be fully qualified"));
};
let context = if let Some((_, context)) = context.split_once("::") {
context
} else {
context
};
let (manager, subnet) = if context == "Sop" {
(None, None)
} else {
let manager_type = context.parse::<ManagerType>()?;
let subnet = match manager_type {
ManagerType::Cop => Some("img"),
ManagerType::Chop => Some("ch"),
ManagerType::Top => Some("topnet"),
_ => None,
};
(Some(manager_type), subnet)
};
let parent = match subnet {
Some(subnet) => {
let parent = self.session.get_manager_node(manager.unwrap())?;
Some(
self.session
.create_node_with(subnet, parent.handle, None, false)?
.handle,
)
}
None => None,
};
let full_name = if parent.is_some() {
operator
} else {
name.as_ref()
};
self.session
.create_node_with(full_name, parent, label.as_ref().map(|v| v.as_ref()), false)
}
pub fn try_create_first(&self) -> Result<HoudiniNode> {
debug_assert!(self.session.is_valid());
let name = self
.get_first_name()?
.ok_or_else(|| crate::HapiError::internal("Library file is empty"))?;
self.create_asset_for_node(name, None)
}
pub fn get_asset_parms(&self, asset: impl AsRef<str>) -> Result<AssetParameters> {
debug_assert!(self.session.is_valid());
let _lock = self.session.lock();
log::debug!("Reading asset parameter list of {}", asset.as_ref());
let asset_name = CString::new(asset.as_ref())?;
let count = crate::ffi::get_asset_def_parm_count(self.lib_id, &asset_name, &self.session)?;
let infos = crate::ffi::get_asset_def_parm_info(
self.lib_id,
&asset_name,
count.parm_count,
&self.session,
)?
.into_iter()
.map(|info| ParmInfo::new(info, self.session.clone(), None));
let values =
crate::ffi::get_asset_def_parm_values(self.lib_id, &asset_name, &self.session, &count)?;
let menus = values.3.into_iter().map(|info| ParmChoiceInfo {
inner: info,
session: self.session.clone(),
});
let values = AssetParmValues {
int: values.0,
float: values.1,
string: values.2,
menus: menus.collect(),
};
Ok(AssetParameters {
infos: infos.collect(),
values,
})
}
}