Skip to main content

hapi_rs/
node.rs

1//! Manipulating Houdini nodes and networks, getting geometry and parameters
2//!
3//! Any Houdini nodes is represented as [`HoudiniNode`] struct and all node-related functions are exposed as
4//! methods on that struct. It has a public `info` filed with [`NodeInfo`] with details about the node.
5//!
6//! Nodes can be created with [`Session::create_node`]
7//!
8//! `HoudiniNode` is [`Clone`], [`Sync`] and [`Send`]
9use std::borrow::Cow;
10use std::path::Path;
11use std::str::FromStr;
12use std::sync::Arc;
13use std::{ffi::CString, ffi::OsStr, fmt::Formatter};
14
15use log::debug;
16
17use crate::pdg::TopNode;
18pub use crate::{
19    errors::Result,
20    ffi::{AssetInfo, GeoInfo, KeyFrame, NodeInfo, ObjectInfo, ParmInfo},
21    geometry::Geometry,
22    parameter::*,
23    session::{CookResult, Session},
24};
25pub use crate::{
26    ffi::raw::{
27        ErrorCode, NodeFlags, NodeType, PresetType, RSTOrder, State, StatusType, StatusVerbosity,
28        TransformComponent,
29    },
30    ffi::{CookOptions, Transform, TransformEuler},
31};
32
33impl std::fmt::Display for NodeType {
34    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
35        f.write_str(match *self {
36            NodeType::Sop => "Sop",
37            NodeType::Obj => "Obj",
38            NodeType::Rop => "Rop",
39            NodeType::Dop => "Dop",
40            NodeType::Cop => "Cop",
41            NodeType::Shop => "Shop",
42            NodeType::Vop => "Vop",
43            NodeType::Chop => "Chop",
44            _ => "Unknown",
45        })
46    }
47}
48
49#[repr(transparent)]
50#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
51pub struct NodeTypeBits(crate::ffi::raw::HAPI_NodeTypeBits);
52#[repr(transparent)]
53#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
54pub struct NodeFlagsBits(crate::ffi::raw::HAPI_NodeFlagsBits);
55
56impl From<NodeTypeBits> for crate::ffi::raw::HAPI_NodeTypeBits {
57    fn from(value: NodeTypeBits) -> Self {
58        value.0
59    }
60}
61
62impl From<NodeFlagsBits> for crate::ffi::raw::HAPI_NodeFlagsBits {
63    fn from(value: NodeFlagsBits) -> Self {
64        value.0
65    }
66}
67
68/// Trait to convert a `NodeType` to a `NodeTypeBits`
69pub trait ToNodeTypeBits {
70    fn to_bits(self) -> NodeTypeBits;
71}
72
73/// Trait to convert a `NodeFlags` to a `NodeFlagsBits`
74pub trait ToNodeFlagsBits {
75    fn to_bits(self) -> NodeFlagsBits;
76}
77
78impl ToNodeTypeBits for NodeType {
79    fn to_bits(self) -> NodeTypeBits {
80        NodeTypeBits(self as crate::ffi::raw::HAPI_NodeTypeBits)
81    }
82}
83
84impl ToNodeFlagsBits for NodeFlags {
85    fn to_bits(self) -> NodeFlagsBits {
86        NodeFlagsBits(self as crate::ffi::raw::HAPI_NodeFlagsBits)
87    }
88}
89
90impl ToNodeTypeBits for NodeTypeBits {
91    fn to_bits(self) -> NodeTypeBits {
92        NodeTypeBits(self.0)
93    }
94}
95
96impl ToNodeFlagsBits for NodeFlagsBits {
97    fn to_bits(self) -> NodeFlagsBits {
98        NodeFlagsBits(self.0)
99    }
100}
101
102impl std::ops::BitOr for NodeFlags {
103    type Output = NodeFlagsBits;
104    fn bitor(self, rhs: Self) -> Self::Output {
105        NodeFlagsBits(
106            (self as crate::ffi::raw::HAPI_NodeFlagsBits)
107                | (rhs as crate::ffi::raw::HAPI_NodeFlagsBits),
108        )
109    }
110}
111
112impl std::ops::BitOr for NodeType {
113    type Output = NodeTypeBits;
114    fn bitor(self, rhs: Self) -> Self::Output {
115        NodeTypeBits(
116            (self as crate::ffi::raw::HAPI_NodeTypeBits)
117                | (rhs as crate::ffi::raw::HAPI_NodeTypeBits),
118        )
119    }
120}
121
122// To allow chaining of bitwise operations
123impl std::ops::BitOr for NodeTypeBits {
124    type Output = NodeTypeBits;
125    fn bitor(self, rhs: Self) -> Self::Output {
126        NodeTypeBits(self.0 | rhs.0)
127    }
128}
129
130impl std::ops::BitOr for NodeFlagsBits {
131    type Output = NodeFlagsBits;
132    fn bitor(self, rhs: Self) -> Self::Output {
133        NodeFlagsBits(self.0 | rhs.0)
134    }
135}
136
137impl std::ops::BitOr<NodeFlags> for NodeFlagsBits {
138    type Output = NodeFlagsBits;
139    fn bitor(self, rhs: NodeFlags) -> Self::Output {
140        NodeFlagsBits(self.0 | (rhs as crate::ffi::raw::HAPI_NodeFlagsBits))
141    }
142}
143
144impl std::ops::BitOr<NodeType> for NodeTypeBits {
145    type Output = NodeTypeBits;
146    fn bitor(self, rhs: NodeType) -> Self::Output {
147        NodeTypeBits(self.0 | (rhs as crate::ffi::raw::HAPI_NodeTypeBits))
148    }
149}
150
151/// Types of Houdini manager nodes (contexts).
152#[derive(Debug, Copy, Clone)]
153#[non_exhaustive]
154pub enum ManagerType {
155    Obj,
156    Chop,
157    Cop,
158    Rop,
159    Top,
160}
161
162impl FromStr for ManagerType {
163    type Err = crate::HapiError;
164
165    fn from_str(val: &str) -> Result<Self> {
166        match val {
167            "Cop2" => Ok(Self::Cop),
168            "Chop" => Ok(Self::Chop),
169            "Top" => Ok(Self::Top),
170            "Object" => Ok(Self::Obj),
171            "Driver" => Ok(Self::Rop),
172            v => Err(crate::HapiError::Internal(format!(
173                "Unknown ManagerType::{v}"
174            ))),
175        }
176    }
177}
178
179impl From<ManagerType> for NodeType {
180    fn from(value: ManagerType) -> Self {
181        use ManagerType::{Chop, Cop, Obj, Rop, Top};
182        match value {
183            Obj => NodeType::Obj,
184            Chop => NodeType::Chop,
185            Cop => NodeType::Cop,
186            Rop => NodeType::Rop,
187            Top => NodeType::Top,
188        }
189    }
190}
191
192// Helper function to return all child nodes of specified type
193fn get_child_node_list(
194    session: &Session,
195    parent: impl Into<NodeHandle>,
196    types: NodeTypeBits,
197    flags: NodeFlagsBits,
198    recursive: bool,
199) -> Result<Vec<NodeHandle>> {
200    let ids =
201        crate::ffi::get_compose_child_node_list(session, parent.into(), types, flags, recursive)?;
202    Ok(ids.into_iter().map(NodeHandle).collect())
203}
204
205// Helper function to return all network type nodes.
206fn find_networks_nodes(
207    session: &Session,
208    types: NodeType,
209    parent: impl Into<NodeHandle>,
210    recursive: bool,
211) -> Result<Vec<HoudiniNode>> {
212    debug_assert!(session.is_valid());
213    get_child_node_list(
214        session,
215        parent,
216        types.to_bits(),
217        NodeFlags::Network.to_bits(),
218        recursive,
219    )
220    .map(|vec| {
221        vec.into_iter()
222            .map(|handle| handle.to_node(session))
223            .collect::<Result<Vec<_>>>()
224    })?
225}
226
227#[derive(Debug, Clone)]
228/// Represents a manager node (OBJ, SOP, etc)
229pub struct ManagerNode {
230    pub session: Session,
231    pub handle: NodeHandle,
232    pub node_type: ManagerType,
233}
234
235impl ManagerNode {
236    /// Find network nodes of given type.
237    pub fn find_network_nodes(&self, types: NodeType) -> Result<Vec<HoudiniNode>> {
238        find_networks_nodes(&self.session, types, self.handle, true)
239    }
240
241    /// Return children nodes of this network.
242    pub fn get_children(&self) -> Result<Vec<NodeHandle>> {
243        get_child_node_list(
244            &self.session,
245            self.handle,
246            NodeType::from(self.node_type).to_bits(),
247            NodeFlags::Any.to_bits(),
248            false,
249        )
250    }
251}
252
253#[repr(transparent)]
254#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
255/// A lightweight handle to a node. Can not be created manually, use [`HoudiniNode`] instead.
256/// Some APIs return a list of such handles for efficiency, for example [`HoudiniNode::get_children_by_type`].
257/// Once you found the node you're looking for, upgrade it to a "full" node type with [`NodeHandle::to_node`].
258pub struct NodeHandle(pub(crate) crate::ffi::raw::HAPI_NodeId);
259
260impl From<NodeHandle> for crate::ffi::raw::HAPI_NodeId {
261    fn from(h: NodeHandle) -> Self {
262        h.0
263    }
264}
265
266impl AsRef<NodeHandle> for HoudiniNode {
267    fn as_ref(&self) -> &NodeHandle {
268        &self.handle
269    }
270}
271
272impl AsRef<NodeHandle> for NodeHandle {
273    fn as_ref(&self) -> &NodeHandle {
274        self
275    }
276}
277
278impl Default for NodeHandle {
279    fn default() -> Self {
280        NodeHandle(-1)
281    }
282}
283
284impl NodeHandle {
285    /// Retrieve info about the node this handle belongs to
286    pub fn info(&self, session: &Session) -> Result<NodeInfo> {
287        NodeInfo::new(session, *self)
288    }
289
290    pub fn asset_info(&self, session: &Session) -> Result<AssetInfo> {
291        Ok(AssetInfo(
292            crate::ffi::get_asset_info(session, *self)?,
293            session.clone().into(),
294        ))
295    }
296
297    /// Returns node's internal path.
298    pub fn path(&self, session: &Session) -> Result<String> {
299        debug_assert!(self.is_valid(session)?, "Invalid {self:?}");
300        crate::ffi::get_node_path(session, *self, None)
301    }
302
303    /// Returns node's path relative to another node.
304    pub fn path_relative(
305        &self,
306        session: &Session,
307        to: impl Into<Option<NodeHandle>>,
308    ) -> Result<String> {
309        debug_assert!(self.is_valid(session)?, "Invalid {self:?}");
310        crate::ffi::get_node_path(session, *self, to.into())
311    }
312
313    /// Check if the handle is valid (node wasn't deleted)
314    pub fn is_valid(&self, session: &Session) -> Result<bool> {
315        let info = self.info(session)?;
316        crate::ffi::is_node_valid(session, &info.0)
317    }
318
319    /// Upgrade the handle to `HoudiniNode`, which has more capabilities.
320    pub fn to_node(&self, session: &Session) -> Result<HoudiniNode> {
321        HoudiniNode::new(session.clone(), *self, None)
322    }
323
324    /// Upgrade the handle to Geometry node.
325    pub fn as_geometry_node(&self, session: &Session) -> Result<Option<Geometry>> {
326        let info = self.info(session)?;
327        match info.node_type() {
328            NodeType::Sop => Ok(Some(Geometry {
329                node: HoudiniNode::new(session.clone(), *self, Some(info))?,
330                info: GeoInfo::from_handle(*self, session)?,
331            })),
332            _ => Ok(None),
333        }
334    }
335
336    /// If this is a handle to a TOP node, returns a [`TopNode`] type.
337    pub fn as_top_node(&self, session: &Session) -> Result<Option<TopNode>> {
338        let node = self.to_node(session)?;
339        match node.info.node_type() {
340            NodeType::Top => Ok(Some(TopNode { node })),
341            _ => Ok(None),
342        }
343    }
344}
345
346#[derive(Clone)]
347/// Represents a Houdini node
348pub struct HoudiniNode {
349    pub handle: NodeHandle,
350    pub session: Session,
351    pub info: Arc<NodeInfo>,
352}
353
354impl PartialEq for HoudiniNode {
355    fn eq(&self, other: &Self) -> bool {
356        self.handle == other.handle && self.session == other.session
357    }
358}
359
360impl std::fmt::Debug for HoudiniNode {
361    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
362        f.debug_struct("HoudiniNode")
363            .field("id", &self.handle.0)
364            .field("type", &self.info.node_type())
365            .field(
366                "path",
367                &self
368                    .path()
369                    .map_or_else(|_| Cow::Borrowed("Node path not available"), Cow::Owned),
370            )
371            .finish_non_exhaustive()
372    }
373}
374
375impl From<HoudiniNode> for NodeHandle {
376    fn from(n: HoudiniNode) -> Self {
377        n.handle
378    }
379}
380
381impl From<HoudiniNode> for Option<NodeHandle> {
382    fn from(n: HoudiniNode) -> Self {
383        Some(n.handle)
384    }
385}
386
387impl From<&HoudiniNode> for Option<NodeHandle> {
388    fn from(n: &HoudiniNode) -> Self {
389        Some(n.handle)
390    }
391}
392
393impl From<&HoudiniNode> for NodeHandle {
394    fn from(n: &HoudiniNode) -> Self {
395        n.handle
396    }
397}
398
399impl HoudiniNode {
400    pub(crate) fn new(
401        session: Session,
402        handle: NodeHandle,
403        info: Option<NodeInfo>,
404    ) -> Result<Self> {
405        let info = Arc::new(match info {
406            None => NodeInfo::new(&session, handle)?,
407            Some(i) => i,
408        });
409        Ok(HoudiniNode {
410            handle,
411            session,
412            info,
413        })
414    }
415
416    /// Convert this node instance into [`TopNode`]
417    #[must_use]
418    pub fn to_top_node(self) -> Option<TopNode> {
419        match self.info.node_type() {
420            NodeType::Top => Some(TopNode { node: self }),
421            _ => None,
422        }
423    }
424    /// Delete the node in this session.
425    pub fn delete(self) -> Result<()> {
426        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
427        crate::ffi::delete_node(self.handle, &self.session)
428    }
429
430    /// Checks if the node valid (not deleted).
431    pub fn is_valid(&self) -> Result<bool> {
432        self.handle.is_valid(&self.session)
433    }
434
435    pub fn name(&self) -> Result<String> {
436        self.info.name()
437    }
438
439    /// Returns node's internal path.
440    pub fn path(&self) -> Result<String> {
441        self.handle.path(&self.session)
442    }
443
444    /// Returns node's path relative to another node.
445    pub fn path_relative(&self, to: impl Into<Option<NodeHandle>>) -> Result<String> {
446        self.handle.path_relative(&self.session, to)
447    }
448
449    /// Start cooking the node. This is a non-blocking call if the session is async.
450    #[must_use = "cook may fail or return errors, check the result"]
451    pub fn cook(&self) -> Result<()> {
452        debug!("Start cooking node: {}", self.path()?);
453        debug_assert!(self.is_valid()?);
454        crate::ffi::cook_node(self, None)
455    }
456
457    /// Start cooking the node and wait until completed.
458    /// In sync mode (single threaded), the error will be available in Err(..) while
459    /// in threaded cooking mode the status will be in [`CookResult`]
460    #[must_use = "cook may fail or return errors, check the result"]
461    pub fn cook_blocking(&self) -> Result<CookResult> {
462        debug!("Start cooking node: {}", self.path()?);
463        debug_assert!(self.is_valid()?);
464        crate::ffi::cook_node(self, None)?;
465        self.session.cook()
466    }
467
468    /// Start cooking with options and wait for result if blocking = true.
469    #[must_use = "cook may fail or return errors, check the result"]
470    pub fn cook_with_options(&self, options: &CookOptions, blocking: bool) -> Result<CookResult> {
471        debug!("Start cooking node: {}", self.path()?);
472        debug_assert!(self.is_valid()?);
473        crate::ffi::cook_node(self, Some(options))?;
474        if blocking {
475            self.session.cook()
476        } else {
477            Ok(CookResult::Succeeded)
478        }
479    }
480
481    /// How many times this node has been cooked.
482    pub fn cook_count(
483        &self,
484        node_types: impl ToNodeTypeBits,
485        node_flags: impl ToNodeFlagsBits,
486        recurse: bool,
487    ) -> Result<i32> {
488        debug_assert!(self.is_valid()?);
489        crate::ffi::get_total_cook_count(self, node_types.to_bits(), node_flags.to_bits(), recurse)
490    }
491
492    /// If the node is of Object type, get the information object about it.
493    pub fn get_object_info(&self) -> Result<ObjectInfo<'_>> {
494        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
495        crate::ffi::get_object_info(&self.session, self.handle)
496            .map(|info| ObjectInfo(info, (&self.session).into()))
497    }
498
499    /// Get a new `NodeInfo` even for this node.
500    pub fn get_info(&self) -> Result<NodeInfo> {
501        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
502        self.handle.info(&self.session)
503    }
504
505    /// Returns information objects about this node children.
506    pub fn get_objects_info(&self) -> Result<Vec<ObjectInfo<'_>>> {
507        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
508        let parent = match self.info.node_type() {
509            NodeType::Obj => self.info.parent_id(),
510            _ => self.handle,
511        };
512        let infos = crate::ffi::get_composed_object_list(&self.session, parent)?;
513        Ok(infos
514            .into_iter()
515            .map(|inner| ObjectInfo(inner, (&self.session).into()))
516            .collect())
517    }
518
519    /// Find all children of this node by type.
520    pub fn get_children_by_type(
521        &self,
522        types: impl ToNodeTypeBits,
523        flags: impl ToNodeFlagsBits,
524        recursive: bool,
525    ) -> Result<Vec<NodeHandle>> {
526        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
527        get_child_node_list(
528            &self.session,
529            self,
530            types.to_bits(),
531            flags.to_bits(),
532            recursive,
533        )
534    }
535
536    /// Get all children of the node, not recursively.
537    pub fn get_children(&self) -> Result<Vec<NodeHandle>> {
538        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
539        get_child_node_list(
540            &self.session,
541            self,
542            NodeType::Any.to_bits(),
543            NodeFlags::Any.to_bits(),
544            false,
545        )
546    }
547
548    /// Get a child node by path.
549    pub fn get_child_by_path(&self, relative_path: &str) -> Result<Option<HoudiniNode>> {
550        self.session
551            .get_node_from_path(relative_path, Some(self.handle))
552    }
553
554    /// Get the node ids for the objects being instanced by an Instance OBJ node.
555    pub fn get_instanced_object_ids(&self) -> Result<Vec<NodeHandle>> {
556        crate::ffi::get_instanced_object_ids(self)
557    }
558
559    /// *Search* for child node by name.
560    pub fn find_child_node(
561        &self,
562        name: impl AsRef<str>,
563        recursive: bool,
564    ) -> Result<Option<HoudiniNode>> {
565        debug_assert!(self.is_valid()?);
566        if !recursive {
567            return self.get_child_by_path(name.as_ref());
568        }
569        for handle in self.get_children_by_type(NodeType::Any, NodeFlags::Any, recursive)? {
570            let info = handle.info(&self.session)?;
571            if info.name()? == name.as_ref() {
572                return Ok(Some(HoudiniNode::new(
573                    self.session.clone(),
574                    handle,
575                    Some(info),
576                )?));
577            }
578        }
579        Ok(None)
580    }
581
582    /// Given if Self is an asset or a subnet SOP node, get its output node at index.
583    // NOTE: Not clear what this is API is for and I could't figure how to use it.
584    #[allow(unused)]
585    pub(crate) fn get_sop_output_node(&self, index: i32) -> Result<NodeHandle> {
586        debug_assert!(self.is_valid()?);
587        crate::ffi::get_sop_output_node(&self.session, self.handle, index)
588    }
589
590    /// Return the node's parent.
591    #[must_use]
592    pub fn parent_node(&self) -> Option<NodeHandle> {
593        let handle = self.info.parent_id();
594        (handle.0 > -1).then_some(handle)
595    }
596
597    /// Find a parameter on the node by name. `Err()` means parameter not found.
598    pub fn parameter(&self, name: &str) -> Result<Parameter> {
599        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
600        let parm_info = ParmInfo::from_parm_name(name, self)?;
601        Ok(Parameter::new(self.handle, parm_info))
602    }
603
604    /// Find a parameter with a specific tag
605    pub fn parameter_with_tag(&self, tag: &str) -> Result<Option<Parameter>> {
606        let tag = CString::new(tag)?;
607        match crate::ffi::get_parm_with_tag(self, &tag)? {
608            -1 => Ok(None),
609            h => {
610                let parm_info =
611                    ParmInfo::from_parm_handle(ParmHandle(h), self.handle, &self.session)?;
612                Ok(Some(Parameter::new(self.handle, parm_info)))
613            }
614        }
615    }
616
617    /// Return all node parameters.
618    pub fn parameters(&self) -> Result<Vec<Parameter>> {
619        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
620        let infos = crate::ffi::get_parameters(self)?;
621        Ok(infos
622            .into_iter()
623            .map(|info| {
624                Parameter::new(self.handle, ParmInfo::new(info, self.session.clone(), None))
625            })
626            .collect())
627    }
628
629    /// If node is an HDA, return [`AssetInfo`] about it.
630    pub fn asset_info(&self) -> Result<AssetInfo> {
631        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
632        self.handle.asset_info(&self.session)
633    }
634    /// Recursively check all nodes for a specific error. Return the error bits found.
635    pub fn check_for_specific_error(&self, error_bits: i32) -> Result<i32> {
636        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
637        crate::ffi::check_for_specific_errors(self, error_bits)
638    }
639
640    /// Compose the cook result (errors and warnings) of all nodes in the network into a string.
641    pub fn get_composed_cook_result_string(&self, verbosity: StatusVerbosity) -> Result<String> {
642        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
643        let bytes = crate::ffi::get_composed_cook_result(self, verbosity)?;
644        String::from_utf8(bytes).map_err(crate::errors::HapiError::from)
645    }
646
647    /// Get the cook errors and warnings on this node as a string
648    pub fn get_cook_result_string(&self, verbosity: StatusVerbosity) -> Result<String> {
649        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
650        let bytes = crate::ffi::get_node_cook_result(self, verbosity)?;
651        Ok(String::from_utf8_lossy(&bytes).to_string())
652    }
653    /// Resets the simulation cache of the asset.
654    pub fn reset_simulation(&self) -> Result<()> {
655        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
656        crate::ffi::reset_simulation(self)
657    }
658
659    /// Return a node connected to given input.
660    pub fn input_node(&self, idx: i32) -> Result<Option<HoudiniNode>> {
661        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
662        crate::ffi::query_node_input(self, idx).map(|idx| {
663            if idx == -1 {
664                None
665            } else {
666                HoudiniNode::new(self.session.clone(), NodeHandle(idx), None).ok()
667            }
668        })
669    }
670
671    /// Give the node a new name.
672    pub fn rename(&self, new_name: impl AsRef<str>) -> Result<()> {
673        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
674        let name = CString::new(new_name.as_ref())?;
675        crate::ffi::rename_node(self, &name)
676    }
677
678    /// Saves the node and all its contents to file
679    pub fn save_to_file(&self, file: impl AsRef<Path>) -> Result<()> {
680        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
681        let filename = CString::new(file.as_ref().to_string_lossy().to_string())?;
682        crate::ffi::save_node_to_file(self.handle, &self.session, &filename)
683    }
684
685    /// Loads and creates a previously saved node and all its contents from given file.
686    pub fn load_from_file(
687        session: &Session,
688        parent: impl Into<Option<NodeHandle>>,
689        label: &str,
690        cook: bool,
691        file: impl AsRef<OsStr>,
692    ) -> Result<HoudiniNode> {
693        debug_assert!(session.is_valid());
694        debug!("Loading node from file {}", file.as_ref().display());
695        let filename = CString::new(file.as_ref().to_string_lossy().to_string())?;
696        let label = CString::new(label)?;
697        let id = crate::ffi::load_node_from_file(parent.into(), session, &label, &filename, cook)?;
698        NodeHandle(id).to_node(session)
699    }
700
701    /// Returns a node preset as bytes.
702    pub fn get_preset(&self, name: &str, preset_type: PresetType) -> Result<Vec<i8>> {
703        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
704        let name = CString::new(name)?;
705        crate::ffi::get_preset(&self.session, self.handle, &name, preset_type)
706    }
707
708    /// Set the preset data to the node.
709    pub fn set_preset(&self, name: &str, preset_type: PresetType, data: &[i8]) -> Result<()> {
710        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
711        let name = CString::new(name)?;
712        crate::ffi::set_preset(&self.session, self.handle, &name, preset_type, data)
713    }
714
715    /// Return Geometry for this node if it's a SOP node,
716    /// otherwise find a child SOP node with display flag and return.
717    pub fn geometry(&self) -> Result<Option<Geometry>> {
718        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
719        match self.info.node_type() {
720            NodeType::Sop => Ok(Some(Geometry {
721                node: self.clone(),
722                info: GeoInfo::from_node(self)?,
723            })),
724            NodeType::Obj => {
725                let info = crate::ffi::get_geo_display_info(self).map(GeoInfo)?;
726                Ok(Some(Geometry {
727                    node: info.node_id().to_node(&self.session)?,
728                    info,
729                }))
730            }
731            _ => Ok(None),
732        }
733    }
734
735    /// Search this node for TOP networks
736    pub fn find_top_networks(&self) -> Result<Vec<HoudiniNode>> {
737        find_networks_nodes(&self.session, NodeType::Top, self, true)
738    }
739
740    /// How many geometry output nodes there is inside an Object or SOP node.
741    pub fn number_of_geo_outputs(&self) -> Result<i32> {
742        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
743        crate::ffi::get_output_geo_count(self)
744    }
745
746    /// Get names of each HDA output
747    pub fn get_output_names(&self) -> Result<Vec<String>> {
748        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
749        crate::ffi::get_output_names(self)
750    }
751
752    /// Return all output nodes as Geometry.
753    pub fn geometry_output_nodes(&self) -> Result<Vec<Geometry>> {
754        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
755        crate::ffi::get_output_geos(self).map(|vec| {
756            vec.into_iter()
757                .map(|inner| {
758                    NodeHandle(inner.nodeId)
759                        .to_node(&self.session)
760                        .map(|node| Geometry {
761                            node,
762                            info: GeoInfo(inner),
763                        })
764                })
765                .collect::<Result<Vec<_>>>()
766        })?
767    }
768
769    /// If node is an Object, return it's transform.
770    pub fn get_transform(
771        &self,
772        rst_order: Option<RSTOrder>,
773        relative_to: impl Into<Option<NodeHandle>>,
774    ) -> Result<Transform> {
775        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
776        crate::ffi::get_object_transform(
777            &self.session,
778            self.handle,
779            relative_to.into(),
780            rst_order.unwrap_or(RSTOrder::Default),
781        )
782        .map(Transform)
783    }
784
785    /// Set transform on the Object
786    pub fn set_transform(&self, transform: &TransformEuler) -> Result<()> {
787        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
788        crate::ffi::set_object_transform(&self.session, self.handle, &transform.0)
789    }
790
791    /// Set keyframes animation on the Object.
792    pub fn set_transform_anim_curve(
793        &self,
794        component: TransformComponent,
795        keys: &[KeyFrame],
796    ) -> Result<()> {
797        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
798        // SAFETY: Both structures have the same memory layout.
799        let keys = unsafe {
800            &*(std::ptr::from_ref::<[crate::ffi::structs::KeyFrame]>(keys)
801                as *const [crate::ffi::raw::HAPI_Keyframe])
802        };
803        crate::ffi::set_transform_anim_curve(&self.session, self.handle, component, keys)
804    }
805
806    /// Connect output of another node into an input on this node.
807    pub fn connect_input<H: Into<NodeHandle>>(
808        &self,
809        input_num: i32,
810        source: H,
811        output_num: i32,
812    ) -> Result<()> {
813        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
814        crate::ffi::connect_node_input(
815            &self.session,
816            self.handle,
817            input_num,
818            source.into(),
819            output_num,
820        )
821    }
822
823    /// Get the nodes currently connected to the given node at the output index.
824    pub fn output_connected_nodes(
825        &self,
826        output_index: i32,
827        search_subnets: bool,
828    ) -> Result<Vec<NodeHandle>> {
829        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
830        crate::ffi::query_node_output_connected_nodes(self, output_index, search_subnets)
831    }
832
833    /// Disconnect a given input index.
834    pub fn disconnect_input(&self, input_index: i32) -> Result<()> {
835        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
836        crate::ffi::disconnect_node_input(self, input_index)
837    }
838
839    /// Disconnect a given output index.
840    pub fn disconnect_outputs(&self, output_index: i32) -> Result<()> {
841        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
842        crate::ffi::disconnect_node_outputs(self, output_index)
843    }
844
845    /// Set display flag on this node.
846    pub fn set_display_flag(&self, on: bool) -> Result<()> {
847        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
848        crate::ffi::set_node_display(&self.session, self.handle, on)
849    }
850
851    /// Get the name of a node's input.
852    pub fn get_input_name(&self, input_index: i32) -> Result<String> {
853        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
854        crate::ffi::get_node_input_name(self, input_index)
855    }
856
857    /// Get the ids of the message nodes specified in the HDA Type Properties
858    pub fn get_message_nodes(&self) -> Result<Vec<NodeHandle>> {
859        debug_assert!(self.is_valid()?, "Invalid node: {}", self.path()?);
860        crate::ffi::get_message_node_ids(self)
861    }
862}