use std::path::Path;
use std::str::FromStr;
use std::sync::Arc;
use std::{ffi::CString, ffi::OsStr, fmt::Formatter};
use log::debug;
use crate::pdg::TopNode;
pub use crate::{
errors::Result,
ffi::{AssetInfo, GeoInfo, KeyFrame, NodeInfo, ObjectInfo, ParmInfo},
geometry::Geometry,
parameter::*,
session::{CookResult, Session},
};
pub use crate::{
ffi::raw::{
ErrorCode, NodeFlags, NodeType, PresetType, RSTOrder, State, StatusType, StatusVerbosity,
TransformComponent,
},
ffi::{CookOptions, Transform, TransformEuler},
};
impl std::fmt::Display for NodeType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match *self {
NodeType::Sop => "Sop",
NodeType::Obj => "Obj",
NodeType::Rop => "Rop",
NodeType::Dop => "Dop",
NodeType::Cop => "Cop",
NodeType::Shop => "Shop",
NodeType::Vop => "Vop",
NodeType::Chop => "Chop",
_ => "Unknown",
})
}
}
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub enum ManagerType {
Obj,
Chop,
Cop,
Rop,
Top,
}
impl FromStr for ManagerType {
type Err = crate::HapiError;
fn from_str(val: &str) -> Result<Self> {
match val {
"Cop2" => Ok(Self::Cop),
"Chop" => Ok(Self::Chop),
"Top" => Ok(Self::Top),
"Object" => Ok(Self::Obj),
"Driver" => Ok(Self::Rop),
v => Err(crate::HapiError::internal(format!(
"Unknown ManagerType::{v}"
))),
}
}
}
impl From<ManagerType> for NodeType {
fn from(value: ManagerType) -> Self {
use ManagerType::*;
match value {
Obj => NodeType::Obj,
Chop => NodeType::Chop,
Cop => NodeType::Cop,
Rop => NodeType::Rop,
Top => NodeType::Top,
}
}
}
fn get_child_node_list(
session: &Session,
parent: impl Into<NodeHandle>,
types: NodeType,
flags: NodeFlags,
recursive: bool,
) -> Result<Vec<NodeHandle>> {
let ids =
crate::ffi::get_compose_child_node_list(session, parent.into(), types, flags, recursive)?;
Ok(ids.into_iter().map(NodeHandle).collect())
}
fn find_networks_nodes(
session: &Session,
types: NodeType,
parent: impl Into<NodeHandle>,
recursive: bool,
) -> Result<Vec<HoudiniNode>> {
debug_assert!(session.is_valid());
get_child_node_list(session, parent, types, NodeFlags::Network, recursive).map(|vec| {
vec.into_iter()
.map(|handle| handle.to_node(session))
.collect::<Result<Vec<_>>>()
})?
}
#[derive(Debug, Clone)]
pub struct ManagerNode {
pub session: Session,
pub handle: NodeHandle,
pub node_type: ManagerType,
}
impl ManagerNode {
pub fn find_network_nodes(&self, types: NodeType) -> Result<Vec<HoudiniNode>> {
find_networks_nodes(&self.session, types, self.handle, true)
}
pub fn get_children(&self) -> Result<Vec<NodeHandle>> {
get_child_node_list(
&self.session,
self.handle,
NodeType::from(self.node_type),
NodeFlags::Any,
false,
)
}
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NodeHandle(pub(crate) crate::ffi::raw::HAPI_NodeId);
impl From<NodeHandle> for crate::ffi::raw::HAPI_NodeId {
fn from(h: NodeHandle) -> Self {
h.0
}
}
impl AsRef<NodeHandle> for HoudiniNode {
fn as_ref(&self) -> &NodeHandle {
&self.handle
}
}
impl AsRef<NodeHandle> for NodeHandle {
fn as_ref(&self) -> &NodeHandle {
self
}
}
impl NodeHandle {
pub fn info(&self, session: &Session) -> Result<NodeInfo> {
NodeInfo::new(session, *self)
}
pub fn is_valid(&self, session: &Session) -> Result<bool> {
let info = self.info(session)?;
crate::ffi::is_node_valid(session, &info.inner)
}
pub fn to_node(&self, session: &Session) -> Result<HoudiniNode> {
HoudiniNode::new(session.clone(), *self, None)
}
pub fn as_geometry_node(&self, session: &Session) -> Result<Option<Geometry>> {
let info = NodeInfo::new(session, *self)?;
match info.node_type() {
NodeType::Sop => Ok(Some(Geometry {
node: HoudiniNode::new(session.clone(), *self, Some(info))?,
info: GeoInfo::from_handle(*self, session)?,
})),
_ => Ok(None),
}
}
pub fn as_top_node(&self, session: &Session) -> Result<Option<TopNode>> {
let node = self.to_node(session)?;
match node.info.node_type() {
NodeType::Top => Ok(Some(TopNode { node })),
_ => Ok(None),
}
}
}
#[derive(Clone)]
pub struct HoudiniNode {
pub handle: NodeHandle,
pub session: Session,
pub info: Arc<NodeInfo>,
}
impl PartialEq for HoudiniNode {
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle && self.session == other.session
}
}
impl std::fmt::Debug for HoudiniNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HoudiniNode")
.field("id", &self.handle.0)
.field("type", &self.info.node_type())
.field(
"path",
&self.path().expect("[HoudiniNode::Debug] node path"),
)
.finish()
}
}
impl From<HoudiniNode> for NodeHandle {
fn from(n: HoudiniNode) -> Self {
n.handle
}
}
impl From<HoudiniNode> for Option<NodeHandle> {
fn from(n: HoudiniNode) -> Self {
Some(n.handle)
}
}
impl From<&HoudiniNode> for Option<NodeHandle> {
fn from(n: &HoudiniNode) -> Self {
Some(n.handle)
}
}
impl From<&HoudiniNode> for NodeHandle {
fn from(n: &HoudiniNode) -> Self {
n.handle
}
}
impl HoudiniNode {
pub(crate) fn new(
session: Session,
handle: NodeHandle,
info: Option<NodeInfo>,
) -> Result<Self> {
let info = Arc::new(match info {
None => NodeInfo::new(&session, handle)?,
Some(i) => i,
});
Ok(HoudiniNode {
handle,
session,
info,
})
}
pub fn to_top_node(self) -> Option<TopNode> {
match self.info.node_type() {
NodeType::Top => Some(TopNode { node: self }),
_ => None,
}
}
pub fn delete(self) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::delete_node(self.handle, &self.session)
}
pub fn is_valid(&self) -> Result<bool> {
self.handle.is_valid(&self.session)
}
pub fn name(&self) -> Result<String> {
self.info.name()
}
pub fn path(&self) -> Result<String> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.name()?);
crate::ffi::get_node_path(&self.session, self.handle, None)
}
pub fn path_relative(&self, to: impl Into<Option<NodeHandle>>) -> Result<String> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::get_node_path(&self.session, self.handle, to.into())
}
pub fn cook(&self) -> Result<()> {
debug!("Start cooking node: {}", self.path()?);
debug_assert!(self.is_valid()?);
crate::ffi::cook_node(self, &CookOptions::default())
}
pub fn cook_blocking(&self) -> Result<CookResult> {
debug!("Start cooking node: {}", self.path()?);
debug_assert!(self.is_valid()?);
crate::ffi::cook_node(self, &CookOptions::default())?;
self.session.cook()
}
pub fn cook_with_options(&self, options: &CookOptions, blocking: bool) -> Result<CookResult> {
debug!("Start cooking node: {}", self.path()?);
debug_assert!(self.is_valid()?);
crate::ffi::cook_node(self, options)?;
if blocking {
self.session.cook()
} else {
Ok(CookResult::Succeeded)
}
}
pub fn cook_count(
&self,
node_types: NodeType,
node_flags: NodeFlags,
recurse: bool,
) -> Result<i32> {
debug_assert!(self.is_valid()?);
crate::ffi::get_total_cook_count(self, node_types, node_flags, recurse)
}
pub fn get_object_info(&self) -> Result<ObjectInfo<'_>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::get_object_info(&self.session, self.handle).map(|info| ObjectInfo {
inner: info,
session: (&self.session).into(),
})
}
pub fn get_info(&self) -> Result<NodeInfo> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
self.handle.info(&self.session)
}
pub fn get_objects_info(&self) -> Result<Vec<ObjectInfo>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
let parent = match self.info.node_type() {
NodeType::Obj => self.info.parent_id(),
_ => self.handle,
};
let infos = crate::ffi::get_composed_object_list(&self.session, parent)?;
Ok(infos
.into_iter()
.map(|inner| ObjectInfo {
inner,
session: (&self.session).into(),
})
.collect())
}
pub fn find_children_by_type(
&self,
types: NodeType,
flags: NodeFlags,
recursive: bool,
) -> Result<Vec<NodeHandle>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
get_child_node_list(&self.session, self, types, flags, recursive)
}
pub fn get_children(&self) -> Result<Vec<NodeHandle>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
get_child_node_list(&self.session, self, NodeType::Any, NodeFlags::Any, false)
}
pub fn get_child_by_path(&self, relative_path: &str) -> Result<Option<HoudiniNode>> {
self.session
.get_node_from_path(relative_path, Some(self.handle))
}
pub fn find_child_node(
&self,
name: impl AsRef<str>,
recursive: bool,
) -> Result<Option<HoudiniNode>> {
debug_assert!(self.is_valid()?);
if !recursive {
return self.get_child_by_path(name.as_ref());
}
for handle in self.find_children_by_type(NodeType::Any, NodeFlags::Any, recursive)? {
let info = handle.info(&self.session)?;
if info.name()? == name.as_ref() {
return Ok(Some(HoudiniNode::new(
self.session.clone(),
handle,
Some(info),
)?));
}
}
Ok(None)
}
pub fn get_sop_output_node(&self, index: i32) -> Result<NodeHandle> {
debug_assert!(self.is_valid()?);
crate::ffi::get_sop_output_node(&self.session, self.handle, index)
}
pub fn parent_node(&self) -> Option<NodeHandle> {
let handle = self.info.parent_id();
(handle.0 > -1).then_some(handle)
}
pub fn parameter(&self, name: &str) -> Result<Parameter> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
let parm_info = ParmInfo::from_parm_name(name, self)?;
Ok(Parameter::new(self.handle, parm_info))
}
pub fn parameter_with_tag(&self, tag: &str) -> Result<Option<Parameter>> {
let tag = CString::new(tag)?;
match crate::ffi::get_parm_with_tag(self, &tag)? {
-1 => Ok(None),
h => {
let parm_info =
ParmInfo::from_parm_handle(ParmHandle(h), self.handle, &self.session)?;
Ok(Some(Parameter::new(self.handle, parm_info)))
}
}
}
pub fn parameters(&self) -> Result<Vec<Parameter>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
let infos = crate::ffi::get_parameters(self)?;
Ok(infos
.into_iter()
.map(|info| {
Parameter::new(self.handle, ParmInfo::new(info, self.session.clone(), None))
})
.collect())
}
pub fn asset_info(&self) -> Result<AssetInfo> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
Ok(AssetInfo {
inner: crate::ffi::get_asset_info(self)?,
session: self.session.clone().into(),
})
}
pub fn check_for_specific_error(&self, error_bits: i32) -> Result<ErrorCode> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::check_for_specific_errors(self, error_bits)
}
pub fn cook_result(&self, verbosity: StatusVerbosity) -> Result<String> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
unsafe { crate::ffi::get_composed_cook_result(self, verbosity) }
}
pub fn reset_simulation(&self) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::reset_simulation(self)
}
pub fn input_node(&self, idx: i32) -> Result<Option<HoudiniNode>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::query_node_input(self, idx).map(|idx| {
if idx == -1 {
None
} else {
HoudiniNode::new(self.session.clone(), NodeHandle(idx), None).ok()
}
})
}
pub fn rename(&self, new_name: impl AsRef<str>) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
let name = CString::new(new_name.as_ref())?;
crate::ffi::rename_node(self, &name)
}
pub fn save_to_file(&self, file: impl AsRef<Path>) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
let filename = CString::new(file.as_ref().to_string_lossy().to_string())?;
crate::ffi::save_node_to_file(self.handle, &self.session, &filename)
}
pub fn load_from_file(
session: &Session,
parent: impl Into<Option<NodeHandle>>,
label: &str,
cook: bool,
file: impl AsRef<OsStr>,
) -> Result<HoudiniNode> {
debug_assert!(session.is_valid());
debug!("Loading node from file {:?}", file.as_ref());
let filename = CString::new(file.as_ref().to_string_lossy().to_string())?;
let label = CString::new(label)?;
let id = crate::ffi::load_node_from_file(parent.into(), session, &label, &filename, cook)?;
NodeHandle(id).to_node(session)
}
pub fn get_preset(&self, name: &str, preset_type: PresetType) -> Result<Vec<i8>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
let name = CString::new(name)?;
crate::ffi::get_preset(&self.session, self.handle, &name, preset_type)
}
pub fn set_preset(&self, name: &str, preset_type: PresetType, data: &[i8]) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
let name = CString::new(name)?;
crate::ffi::set_preset(&self.session, self.handle, &name, preset_type, data)
}
pub fn geometry(&self) -> Result<Option<Geometry>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
match self.info.node_type() {
NodeType::Sop => Ok(Some(Geometry {
node: self.clone(),
info: GeoInfo::from_node(self)?,
})),
NodeType::Obj => {
let info = crate::ffi::get_geo_display_info(self).map(|inner| GeoInfo { inner })?;
Ok(Some(Geometry {
node: info.node_id().to_node(&self.session)?,
info,
}))
}
_ => Ok(None),
}
}
pub fn find_top_networks(&self) -> Result<Vec<HoudiniNode>> {
find_networks_nodes(&self.session, NodeType::Top, self, true)
}
pub fn number_of_geo_outputs(&self) -> Result<i32> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::get_output_geo_count(self)
}
pub fn get_output_names(&self) -> Result<Vec<String>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::get_output_names(self)
}
pub fn geometry_output_nodes(&self) -> Result<Vec<Geometry>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::get_output_geos(self).map(|vec| {
vec.into_iter()
.map(|inner| {
NodeHandle(inner.nodeId)
.to_node(&self.session)
.map(|node| Geometry {
node,
info: GeoInfo { inner },
})
})
.collect::<Result<Vec<_>>>()
})?
}
pub fn get_transform(
&self,
rst_order: Option<RSTOrder>,
relative_to: impl Into<Option<NodeHandle>>,
) -> Result<Transform> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::get_object_transform(
&self.session,
self.handle,
relative_to.into(),
rst_order.unwrap_or(RSTOrder::Default),
)
.map(|inner| Transform { inner })
}
pub fn set_transform(&self, transform: &TransformEuler) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::set_object_transform(&self.session, self.handle, &transform.inner)
}
pub fn set_transform_anim_curve(
&self,
component: TransformComponent,
keys: &[KeyFrame],
) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
let keys =
unsafe { std::mem::transmute::<&[KeyFrame], &[crate::ffi::raw::HAPI_Keyframe]>(keys) };
crate::ffi::set_transform_anim_curve(&self.session, self.handle, component, keys)
}
pub fn connect_input<H: Into<NodeHandle>>(
&self,
input_num: i32,
source: H,
output_num: i32,
) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::connect_node_input(
&self.session,
self.handle,
input_num,
source.into(),
output_num,
)
}
pub fn output_connected_nodes(
&self,
output_index: i32,
search_subnets: bool,
) -> Result<Vec<NodeHandle>> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::query_node_output_connected_nodes(self, output_index, search_subnets)
}
pub fn disconnect_input(&self, input_index: i32) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::disconnect_node_input(self, input_index)
}
pub fn disconnect_outputs(&self, output_index: i32) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::disconnect_node_outputs(self, output_index)
}
pub fn set_display_flag(&self, on: bool) -> Result<()> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::set_node_display(&self.session, self.handle, on)
}
pub fn get_input_name(&self, input_index: i32) -> Result<String> {
debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
crate::ffi::get_node_input_name(self, input_index)
}
}