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