hapi_rs/
geometry.rs

1//! Access to geometry data, attributes, reading and writing geometry to and from disk
2//!
3//!
4
5use crate::attribute::*;
6use crate::errors::Result;
7pub use crate::ffi::{
8    AttributeInfo, BoxInfo, CookOptions, CurveInfo, GeoInfo, InputCurveInfo, PartInfo, SphereInfo,
9    Transform, VolumeInfo, VolumeTileInfo, VolumeVisualInfo, enums::*,
10};
11use crate::material::Material;
12use crate::node::{HoudiniNode, NodeHandle};
13use crate::stringhandle::StringArray;
14use crate::volume::{Tile, VolumeBounds, VolumeStorage};
15use std::ffi::{CStr, CString};
16
17#[derive(Debug, Clone)]
18/// Represents a SOP node with methods for manipulating geometry.
19pub struct Geometry {
20    pub node: HoudiniNode,
21    pub(crate) info: GeoInfo,
22}
23
24/// In-memory geometry format
25#[derive(Debug)]
26pub enum GeoFormat {
27    Geo,
28    Bgeo,
29    Obj,
30}
31
32#[derive(Debug)]
33/// Face materials
34pub enum Materials {
35    /// Material was assigned at object level or all faces on geometry share the same material
36    Single(Material),
37    /// Materials assigned per-face
38    Multiple(Vec<Material>),
39}
40
41impl GeoFormat {
42    const fn as_cstr(&self) -> &'static CStr {
43        unsafe {
44            CStr::from_bytes_with_nul_unchecked(match *self {
45                GeoFormat::Geo => b".geo\0",
46                GeoFormat::Bgeo => b".bgeo\0",
47                GeoFormat::Obj => b".obj\0",
48            })
49        }
50    }
51}
52
53/// Common geometry attribute names
54#[derive(Debug)]
55pub enum AttributeName {
56    Cd,
57    P,
58    N,
59    Uv,
60    TangentU,
61    TangentV,
62    Scale,
63    Name,
64    User(CString),
65}
66
67impl TryFrom<&str> for AttributeName {
68    type Error = std::ffi::NulError;
69
70    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
71        CString::new(value).map(AttributeName::User)
72    }
73}
74
75impl TryFrom<String> for AttributeName {
76    type Error = std::ffi::NulError;
77
78    fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
79        CString::new(value).map(AttributeName::User)
80    }
81}
82
83impl From<&CStr> for AttributeName {
84    fn from(value: &CStr) -> Self {
85        AttributeName::User(value.to_owned())
86    }
87}
88
89impl From<AttributeName> for CString {
90    fn from(name: AttributeName) -> Self {
91        macro_rules! cstr {
92            ($attr:expr) => {
93                unsafe { CStr::from_bytes_with_nul_unchecked($attr).to_owned() }
94            };
95        }
96        match name {
97            AttributeName::Cd => cstr!(crate::raw::HAPI_ATTRIB_COLOR),
98            AttributeName::P => cstr!(crate::raw::HAPI_ATTRIB_POSITION),
99            AttributeName::N => cstr!(crate::raw::HAPI_ATTRIB_NORMAL),
100            AttributeName::Uv => cstr!(crate::raw::HAPI_ATTRIB_UV),
101            AttributeName::TangentU => cstr!(crate::raw::HAPI_ATTRIB_TANGENT),
102            AttributeName::TangentV => cstr!(crate::raw::HAPI_ATTRIB_TANGENT2),
103            AttributeName::Scale => cstr!(crate::raw::HAPI_ATTRIB_SCALE),
104            AttributeName::Name => cstr!(crate::raw::HAPI_ATTRIB_NAME),
105            AttributeName::User(val) => val,
106        }
107    }
108}
109
110impl Geometry {
111    /// Get geometry partition info by index.
112    pub fn part_info(&self, part_id: i32) -> Result<PartInfo> {
113        self.assert_node_cooked();
114        crate::ffi::get_part_info(&self.node, part_id).map(PartInfo)
115    }
116
117    pub fn volume_info(&self, part_id: i32) -> Result<VolumeInfo> {
118        self.assert_node_cooked();
119        crate::ffi::get_volume_info(&self.node, part_id).map(VolumeInfo)
120    }
121
122    pub fn set_volume_info(&self, part_id: i32, info: &VolumeInfo) -> Result<()> {
123        crate::ffi::set_volume_info(&self.node, part_id, &info.0)
124    }
125
126    #[inline(always)]
127    fn assert_node_cooked(&self) {
128        debug_assert!(
129            crate::ffi::get_node_info(self.node.handle, &self.node.session)
130                .expect("NodeInfo")
131                .totalCookCount
132                > 0,
133            "Node not cooked"
134        );
135    }
136
137    pub fn volume_bounds(&self, part_id: i32) -> Result<VolumeBounds> {
138        self.assert_node_cooked();
139        crate::ffi::get_volume_bounds(&self.node, part_id)
140    }
141    pub fn get_volume_visual_info(&self, part_id: i32) -> Result<VolumeVisualInfo> {
142        crate::ffi::get_volume_visual_info(&self.node, part_id).map(VolumeVisualInfo)
143    }
144
145    /// Get information about Node's geometry.
146    /// Note: The node must be cooked before calling this method.
147    pub fn geo_info(&self) -> Result<GeoInfo> {
148        self.assert_node_cooked();
149        GeoInfo::from_node(&self.node)
150    }
151
152    pub fn set_part_info(&self, info: &PartInfo) -> Result<()> {
153        debug_assert!(self.node.is_valid()?);
154        crate::ffi::set_part_info(&self.node, info)
155    }
156
157    pub fn box_info(&self, part_id: i32) -> Result<BoxInfo> {
158        self.assert_node_cooked();
159        crate::ffi::get_box_info(self.node.handle, &self.node.session, part_id).map(BoxInfo)
160    }
161
162    pub fn sphere_info(&self, part_id: i32) -> Result<SphereInfo> {
163        self.assert_node_cooked();
164        crate::ffi::get_sphere_info(self.node.handle, &self.node.session, part_id).map(SphereInfo)
165    }
166
167    pub fn set_curve_info(&self, part_id: i32, info: &CurveInfo) -> Result<()> {
168        debug_assert!(self.node.is_valid()?);
169        crate::ffi::set_curve_info(&self.node, part_id, info)
170    }
171
172    pub fn set_input_curve_info(&self, part_id: i32, info: &InputCurveInfo) -> Result<()> {
173        debug_assert!(self.node.is_valid()?);
174        crate::ffi::set_input_curve_info(&self.node, part_id, info)
175    }
176
177    pub fn get_input_curve_info(&self, part_id: i32) -> Result<InputCurveInfo> {
178        debug_assert!(self.node.is_valid()?);
179        crate::ffi::get_input_curve_info(&self.node, part_id).map(InputCurveInfo)
180    }
181
182    pub fn set_input_curve_positions(&self, part_id: i32, positions: &[f32]) -> Result<()> {
183        crate::ffi::set_input_curve_positions(
184            &self.node,
185            part_id,
186            positions,
187            0,
188            positions.len() as i32,
189        )
190    }
191
192    pub fn set_input_curve_transform(
193        &self,
194        part_id: i32,
195        positions: &[f32],
196        rotation: &[f32],
197        scale: &[f32],
198    ) -> Result<()> {
199        crate::ffi::set_input_curve_transform(&self.node, part_id, positions, rotation, scale)
200    }
201
202    pub fn set_curve_counts(&self, part_id: i32, count: &[i32]) -> Result<()> {
203        debug_assert!(self.node.is_valid()?);
204        crate::ffi::set_curve_counts(&self.node, part_id, count)
205    }
206
207    pub fn set_curve_knots(&self, part_id: i32, knots: &[f32]) -> Result<()> {
208        debug_assert!(self.node.is_valid()?);
209        crate::ffi::set_curve_knots(&self.node, part_id, knots)
210    }
211
212    pub fn set_vertex_list(&self, part_id: i32, list: impl AsRef<[i32]>) -> Result<()> {
213        debug_assert!(self.node.is_valid()?);
214        crate::ffi::set_geo_vertex_list(&self.node, part_id, list.as_ref())
215    }
216
217    pub fn set_face_counts(&self, part_id: i32, list: impl AsRef<[i32]>) -> Result<()> {
218        debug_assert!(self.node.is_valid()?);
219        crate::ffi::set_geo_face_counts(&self.node, part_id, list.as_ref())
220    }
221
222    pub fn update(&mut self) -> Result<()> {
223        self.info = self.geo_info()?;
224        Ok(())
225    }
226
227    pub fn curve_info(&self, part_id: i32) -> Result<CurveInfo> {
228        self.assert_node_cooked();
229        crate::ffi::get_curve_info(&self.node, part_id).map(CurveInfo)
230    }
231
232    /// Retrieve the number of vertices for each curve in the part.
233    pub fn curve_counts(&self, part_id: i32, start: i32, length: i32) -> Result<Vec<i32>> {
234        self.assert_node_cooked();
235        crate::ffi::get_curve_counts(&self.node, part_id, start, length)
236    }
237
238    /// Retrieve the orders for each curve in the part if the curve has varying order.
239    pub fn curve_orders(&self, part_id: i32, start: i32, length: i32) -> Result<Vec<i32>> {
240        self.assert_node_cooked();
241        crate::ffi::get_curve_orders(&self.node, part_id, start, length)
242    }
243
244    /// Retrieve the knots of the curves in this part.
245    pub fn curve_knots(&self, part_id: i32, start: i32, length: i32) -> Result<Vec<f32>> {
246        self.assert_node_cooked();
247        crate::ffi::get_curve_knots(&self.node, part_id, start, length)
248    }
249
250    /// Get array containing the vertex-point associations where the
251    /// ith element in the array is the point index the ith vertex
252    /// associates with.
253    pub fn vertex_list(&self, part: &PartInfo) -> Result<Vec<i32>> {
254        self.assert_node_cooked();
255        crate::ffi::get_geo_vertex_list(
256            &self.node.session,
257            self.node.handle,
258            part.part_id(),
259            0,
260            part.vertex_count(),
261        )
262    }
263
264    pub fn partitions(&self) -> Result<Vec<PartInfo>> {
265        self.assert_node_cooked();
266        (0..self.geo_info()?.part_count())
267            .map(|part| self.part_info(part))
268            .collect()
269    }
270
271    pub fn get_face_counts(&self, part: &PartInfo) -> Result<Vec<i32>> {
272        self.assert_node_cooked();
273        crate::ffi::get_face_counts(
274            &self.node.session,
275            self.node.handle,
276            part.part_id(),
277            0,
278            part.face_count(),
279        )
280    }
281
282    /// Return material nodes applied to geometry.
283    pub fn get_materials(&self, part: &PartInfo) -> Result<Option<Materials>> {
284        self.assert_node_cooked();
285        let (all_the_same, mats) = crate::ffi::get_material_node_ids_on_faces(
286            &self.node.session,
287            self.node.handle,
288            part.face_count(),
289            part.part_id(),
290        )?;
291        if all_the_same {
292            if mats[0] == -1 {
293                Ok(None)
294            } else {
295                let mat_node = NodeHandle(mats[0]);
296                let info = crate::ffi::get_material_info(&self.node.session, mat_node)?;
297                Ok(Some(Materials::Single(Material {
298                    session: self.node.session.clone(),
299                    info,
300                })))
301            }
302        } else {
303            let session = self.node.session.clone();
304            let mats = mats
305                .into_iter()
306                .map(|id| {
307                    crate::ffi::get_material_info(&session, NodeHandle(id)).map(|info| Material {
308                        session: session.clone(),
309                        info,
310                    })
311                })
312                .collect::<Result<Vec<_>>>();
313            mats.map(|vec| Some(Materials::Multiple(vec)))
314        }
315    }
316
317    /// Get geometry group names by type.
318    pub fn get_group_names(&self, group_type: GroupType) -> Result<StringArray> {
319        self.assert_node_cooked();
320        let count = match group_type {
321            GroupType::Point => self.info.point_group_count(),
322            GroupType::Prim => self.info.primitive_group_count(),
323            GroupType::Edge => self.info.edge_group_count(),
324            _ => unreachable!("Impossible GroupType value"),
325        };
326        crate::ffi::get_group_names(&self.node, group_type, count)
327    }
328
329    pub fn get_edge_count_of_edge_group(&self, group: &str, part_id: i32) -> Result<i32> {
330        self.assert_node_cooked();
331        let group = CString::new(group)?;
332        crate::ffi::get_edge_count_of_edge_group(
333            &self.node.session,
334            self.node.handle,
335            &group,
336            part_id,
337        )
338    }
339    /// Get num geometry elements by type (points, prims, vertices).
340    pub fn get_element_count_by_owner(
341        &self,
342        part: &PartInfo,
343        owner: AttributeOwner,
344    ) -> Result<i32> {
345        crate::ffi::get_element_count_by_attribute_owner(part, owner)
346    }
347
348    /// Get number of attributes by type.
349    pub fn get_attribute_count_by_owner(
350        &self,
351        part: &PartInfo,
352        owner: AttributeOwner,
353    ) -> Result<i32> {
354        crate::ffi::get_attribute_count_by_owner(part, owner)
355    }
356
357    pub fn get_attribute_names(
358        &self,
359        owner: AttributeOwner,
360        part: &PartInfo,
361    ) -> Result<StringArray> {
362        self.assert_node_cooked();
363        let counts = part.attribute_counts();
364        let count = match owner {
365            AttributeOwner::Invalid => panic!("Invalid AttributeOwner"),
366            AttributeOwner::Vertex => counts[0],
367            AttributeOwner::Point => counts[1],
368            AttributeOwner::Prim => counts[2],
369            AttributeOwner::Detail => counts[3],
370            AttributeOwner::Max => panic!("Invalid AttributeOwner"),
371        };
372        crate::ffi::get_attribute_names(&self.node, part.part_id(), count, owner)
373    }
374
375    /// Convenient method for getting the P attribute
376    pub fn get_position_attribute(&self, part_id: i32) -> Result<NumericAttr<f32>> {
377        self.assert_node_cooked();
378        let name = CString::from(AttributeName::P);
379        let info = AttributeInfo::new(&self.node, part_id, AttributeOwner::Point, name.as_c_str())?;
380        Ok(NumericAttr::new(name, info, self.node.clone()))
381    }
382
383    /// Retrieve information about a geometry attribute.
384    pub fn get_attribute_info(
385        &self,
386        part_id: i32,
387        owner: AttributeOwner,
388        name: impl TryInto<AttributeName, Error = impl Into<crate::HapiError>>,
389    ) -> Result<AttributeInfo> {
390        let name: AttributeName = name.try_into().map_err(Into::into)?;
391        let name: CString = name.into();
392        AttributeInfo::new(&self.node, part_id, owner, &name)
393    }
394
395    /// Get geometry attribute by name and owner.
396    pub fn get_attribute<T>(
397        &self,
398        part_id: i32,
399        owner: AttributeOwner,
400        name: T,
401    ) -> Result<Option<Attribute>>
402    where
403        T: TryInto<AttributeName>,
404        T::Error: Into<crate::HapiError>,
405    {
406        self.assert_node_cooked();
407        let name: AttributeName = name.try_into().map_err(Into::into)?;
408        let name: CString = name.into();
409        let info = AttributeInfo::new(&self.node, part_id, owner, &name)?;
410        let storage = info.storage();
411        if !info.exists() {
412            return Ok(None);
413        }
414        let node = self.node.clone();
415        let attr_obj: Box<dyn AnyAttribWrapper> = match storage {
416            s @ (StorageType::Invalid | StorageType::Max) => {
417                panic!("Invalid attribute storage {name:?}: {s:?}")
418            }
419            StorageType::Int => NumericAttr::<i32>::new(name, info, node).boxed(),
420            StorageType::Int64 => NumericAttr::<i64>::new(name, info, node).boxed(),
421            StorageType::Float => NumericAttr::<f32>::new(name, info, node).boxed(),
422            StorageType::Float64 => NumericAttr::<f64>::new(name, info, node).boxed(),
423            StorageType::String => StringAttr::new(name, info, node).boxed(),
424            StorageType::Uint8 => NumericAttr::<u8>::new(name, info, node).boxed(),
425            StorageType::Int8 => NumericAttr::<i8>::new(name, info, node).boxed(),
426            StorageType::Int16 => NumericAttr::<i16>::new(name, info, node).boxed(),
427            StorageType::IntArray => NumericArrayAttr::<i32>::new(name, info, node).boxed(),
428            StorageType::Int64Array => NumericArrayAttr::<i64>::new(name, info, node).boxed(),
429            StorageType::FloatArray => NumericArrayAttr::<f32>::new(name, info, node).boxed(),
430            StorageType::Float64Array => NumericArrayAttr::<f64>::new(name, info, node).boxed(),
431            StorageType::StringArray => StringArrayAttr::new(name, info, node).boxed(),
432            StorageType::Uint8Array => NumericArrayAttr::<u8>::new(name, info, node).boxed(),
433            StorageType::Int8Array => NumericArrayAttr::<i8>::new(name, info, node).boxed(),
434            StorageType::Int16Array => NumericArrayAttr::<i16>::new(name, info, node).boxed(),
435            StorageType::Dictionary => DictionaryAttr::new(name, info, node).boxed(),
436            StorageType::DictionaryArray => DictionaryArrayAttr::new(name, info, node).boxed(),
437        };
438        Ok(Some(Attribute::new(attr_obj)))
439    }
440
441    /// Add a new numeric attribute to geometry.
442    pub fn add_numeric_attribute<T: AttribAccess>(
443        &self,
444        name: &str,
445        part_id: i32,
446        info: AttributeInfo,
447    ) -> Result<NumericAttr<T>> {
448        debug_assert_eq!(info.storage(), T::storage());
449        debug_assert!(
450            info.tuple_size() > 0,
451            "attribute \"{}\" tuple_size must be > 0",
452            name
453        );
454        log::debug!("Adding numeric geometry attriubute: {name}");
455        let name = CString::new(name)?;
456        crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
457        Ok(NumericAttr::<T>::new(name, info, self.node.clone()))
458    }
459
460    /// Add a new numeric array attribute to geometry.
461    pub fn add_numeric_array_attribute<T>(
462        &self,
463        name: &str,
464        part_id: i32,
465        info: AttributeInfo,
466    ) -> Result<NumericArrayAttr<T>>
467    where
468        T: AttribAccess,
469        [T]: ToOwned<Owned = Vec<T>>,
470    {
471        debug_assert_eq!(info.storage(), T::storage_array());
472        debug_assert!(
473            info.tuple_size() > 0,
474            "AttributeInfo::tuple_size must be 1 for array attributes"
475        );
476        log::debug!("Adding numeric array geometry attriubute: {name}");
477        let name = CString::new(name)?;
478        crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
479        Ok(NumericArrayAttr::<T>::new(name, info, self.node.clone()))
480    }
481
482    /// Add a new string attribute to geometry
483    pub fn add_string_attribute(
484        &self,
485        name: &str,
486        part_id: i32,
487        info: AttributeInfo,
488    ) -> Result<StringAttr> {
489        debug_assert!(self.node.is_valid()?);
490        debug_assert_eq!(info.storage(), StorageType::String);
491        debug_assert!(
492            info.tuple_size() > 0,
493            "attribute \"{}\" tuple_size must be > 0",
494            name
495        );
496        log::debug!("Adding string geometry attriubute: {name}");
497        let name = CString::new(name)?;
498        crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
499        Ok(StringAttr::new(name, info, self.node.clone()))
500    }
501
502    /// Add a new string array attribute to geometry.
503    pub fn add_string_array_attribute(
504        &self,
505        name: &str,
506        part_id: i32,
507        info: AttributeInfo,
508    ) -> Result<StringArrayAttr> {
509        debug_assert!(self.node.is_valid()?);
510        debug_assert_eq!(info.storage(), StorageType::StringArray);
511        debug_assert!(
512            info.tuple_size() > 0,
513            "attribute \"{}\" tuple_size must be > 0",
514            name
515        );
516        log::debug!("Adding string array geometry attriubute: {name}");
517        let name = CString::new(name)?;
518        crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
519        Ok(StringArrayAttr::new(name, info, self.node.clone()))
520    }
521
522    /// Add a new dictionary attribute to geometry
523    pub fn add_dictionary_attribute(
524        &self,
525        name: &str,
526        part_id: i32,
527        info: AttributeInfo,
528    ) -> Result<DictionaryAttr> {
529        debug_assert!(self.node.is_valid()?);
530        debug_assert_eq!(info.storage(), StorageType::Dictionary);
531        debug_assert!(
532            info.tuple_size() > 0,
533            "attribute \"{}\" tuple_size must be > 0",
534            name
535        );
536        log::debug!("Adding dictionary geometry attriubute: {name}");
537        let name = CString::new(name)?;
538        crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
539        Ok(DictionaryAttr::new(name, info, self.node.clone()))
540    }
541
542    /// Add a new dictionary attribute to geometry
543    pub fn add_dictionary_array_attribute(
544        &self,
545        name: &str,
546        part_id: i32,
547        info: AttributeInfo,
548    ) -> Result<DictionaryArrayAttr> {
549        debug_assert!(self.node.is_valid()?);
550        debug_assert_eq!(info.storage(), StorageType::DictionaryArray);
551        debug_assert!(
552            info.tuple_size() > 0,
553            "attribute \"{}\" tuple_size must be > 0",
554            name
555        );
556        log::debug!("Adding dictionary array geometry attriubute: {name}");
557        let name = CString::new(name)?;
558        crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
559        Ok(DictionaryArrayAttr::new(name, info, self.node.clone()))
560    }
561
562    /// Create a new geometry group.
563    pub fn add_group(
564        &self,
565        part_id: i32,
566        group_type: GroupType,
567        group_name: &str,
568        membership: Option<&[i32]>,
569    ) -> Result<()> {
570        debug_assert!(self.node.is_valid()?);
571        let group_name = CString::new(group_name)?;
572        crate::ffi::add_group(
573            &self.node.session,
574            self.node.handle,
575            part_id,
576            group_type,
577            &group_name,
578        )?;
579        match membership {
580            None => Ok(()),
581            Some(array) => crate::ffi::set_group_membership(
582                &self.node.session,
583                self.node.handle,
584                part_id,
585                group_type,
586                &group_name,
587                array,
588            ),
589        }
590    }
591
592    /// Delete a geometry group.
593    pub fn delete_group(
594        &self,
595        part_id: i32,
596        group_type: GroupType,
597        group_name: &str,
598    ) -> Result<()> {
599        debug_assert!(self.node.is_valid()?);
600        let group_name = CString::new(group_name)?;
601        crate::ffi::delete_group(
602            &self.node.session,
603            self.node.handle,
604            part_id,
605            group_type,
606            &group_name,
607        )
608    }
609
610    /// Set element membership for a group.
611    pub fn set_group_membership(
612        &self,
613        part_id: i32,
614        group_type: GroupType,
615        group_name: &str,
616        array: &[i32],
617    ) -> Result<()> {
618        debug_assert!(self.node.is_valid()?);
619        let group_name = CString::new(group_name)?;
620        crate::ffi::set_group_membership(
621            &self.node.session,
622            self.node.handle,
623            part_id,
624            group_type,
625            &group_name,
626            array,
627        )
628    }
629
630    /// Get element membership for a group.
631    pub fn get_group_membership(
632        &self,
633        part: &PartInfo,
634        group_type: GroupType,
635        group_name: &str,
636    ) -> Result<Vec<i32>> {
637        self.assert_node_cooked();
638        let group_name = CString::new(group_name)?;
639        crate::ffi::get_group_membership(
640            &self.node.session,
641            self.node.handle,
642            part.part_id(),
643            group_type,
644            &group_name,
645            part.element_count_by_group(group_type),
646        )
647    }
648
649    /// Number of geometry groups by type.
650    pub fn group_count_by_type(&self, group_type: GroupType) -> Result<i32> {
651        self.assert_node_cooked();
652        Ok(crate::ffi::get_group_count_by_type(&self.info, group_type))
653    }
654
655    pub fn get_instanced_part_ids(&self, part: &PartInfo) -> Result<Vec<i32>> {
656        self.assert_node_cooked();
657        crate::ffi::get_instanced_part_ids(
658            &self.node.session,
659            self.node.handle,
660            part.part_id(),
661            part.instanced_part_count(),
662        )
663    }
664
665    /// Get group membership for a packed instance part.
666    /// This functions allows you to get the group membership for a specific packed primitive part.
667    pub fn get_group_membership_on_packed_instance_part(
668        &self,
669        part: &PartInfo,
670        group_type: GroupType,
671        group_name: &CStr,
672    ) -> Result<(bool, Vec<i32>)> {
673        crate::ffi::get_group_membership_on_packed_instance_part(
674            &self.node, part, group_type, group_name,
675        )
676    }
677
678    pub fn get_group_count_on_packed_instance(&self, part: &PartInfo) -> Result<(i32, i32)> {
679        self.assert_node_cooked();
680        crate::ffi::get_group_count_on_instance_part(
681            &self.node.session,
682            self.node.handle,
683            part.part_id(),
684        )
685    }
686
687    pub fn get_instance_part_groups_names(
688        &self,
689        group: GroupType,
690        part_id: i32,
691    ) -> Result<StringArray> {
692        self.assert_node_cooked();
693        crate::ffi::get_group_names_on_instance_part(
694            &self.node.session,
695            self.node.handle,
696            part_id,
697            group,
698        )
699    }
700
701    pub fn get_instance_part_transforms(
702        &self,
703        part: &PartInfo,
704        order: RSTOrder,
705    ) -> Result<Vec<Transform>> {
706        self.assert_node_cooked();
707        crate::ffi::get_instanced_part_transforms(
708            &self.node.session,
709            self.node.handle,
710            part.part_id(),
711            order,
712            part.instance_count(),
713        )
714        .map(|vec| vec.into_iter().map(Transform).collect())
715    }
716
717    /// Save geometry to a file.
718    pub fn save_to_file(&self, filepath: &str) -> Result<()> {
719        self.assert_node_cooked();
720        let path = CString::new(filepath)?;
721        crate::ffi::save_geo_to_file(&self.node, &path)
722    }
723
724    /// Load geometry from a file.
725    pub fn load_from_file(&self, filepath: &str) -> Result<()> {
726        debug_assert!(self.node.is_valid()?);
727        let path = CString::new(filepath)?;
728        crate::ffi::load_geo_from_file(&self.node, &path)
729    }
730
731    /// Commit geometry edits to the node.
732    pub fn commit(&self) -> Result<()> {
733        debug_assert!(self.node.is_valid()?);
734        log::debug!("Commiting geometry changes");
735        crate::ffi::commit_geo(&self.node)
736    }
737
738    /// Revert last geometry edits
739    pub fn revert(&self) -> Result<()> {
740        debug_assert!(self.node.is_valid()?);
741        crate::ffi::revert_geo(&self.node)
742    }
743
744    /// Serialize node's geometry to bytes.
745    pub fn save_to_memory(&self, format: GeoFormat) -> Result<Vec<i8>> {
746        self.assert_node_cooked();
747        crate::ffi::save_geo_to_memory(&self.node.session, self.node.handle, format.as_cstr())
748    }
749
750    /// Load geometry from a given buffer into this node.
751    pub fn load_from_memory(&self, data: &[i8], format: GeoFormat) -> Result<()> {
752        crate::ffi::load_geo_from_memory(
753            &self.node.session,
754            self.node.handle,
755            data,
756            format.as_cstr(),
757        )
758    }
759
760    pub fn read_volume_tile<T: VolumeStorage>(
761        &self,
762        part: i32,
763        fill: T,
764        tile: &VolumeTileInfo,
765        values: &mut [T],
766    ) -> Result<()> {
767        self.assert_node_cooked();
768        T::read_tile(&self.node, part, fill, values, &tile.0)
769    }
770
771    pub fn write_volume_tile<T: VolumeStorage>(
772        &self,
773        part: i32,
774        tile: &VolumeTileInfo,
775        values: &[T],
776    ) -> Result<()> {
777        self.assert_node_cooked();
778        T::write_tile(&self.node, part, values, &tile.0)
779    }
780
781    pub fn read_volume_voxel<T: VolumeStorage>(
782        &self,
783        part: i32,
784        x_index: i32,
785        y_index: i32,
786        z_index: i32,
787        values: &mut [T],
788    ) -> Result<()> {
789        self.assert_node_cooked();
790        T::read_voxel(&self.node, part, x_index, y_index, z_index, values)
791    }
792
793    pub fn write_volume_voxel<T: VolumeStorage>(
794        &self,
795        part: i32,
796        x_index: i32,
797        y_index: i32,
798        z_index: i32,
799        values: &[T],
800    ) -> Result<()> {
801        self.assert_node_cooked();
802        T::write_voxel(&self.node, part, x_index, y_index, z_index, values)
803    }
804
805    /// Iterate over volume tiles and apply a function to each tile.
806    pub fn foreach_volume_tile(
807        &self,
808        part: i32,
809        info: &VolumeInfo,
810        callback: impl Fn(Tile),
811    ) -> Result<()> {
812        self.assert_node_cooked();
813        let tile_size = (info.tile_size().pow(3) * info.tuple_size()) as usize;
814        crate::volume::iterate_tiles(&self.node, part, tile_size, callback)
815    }
816
817    pub fn get_heightfield_data(&self, part_id: i32, volume_info: &VolumeInfo) -> Result<Vec<f32>> {
818        self.assert_node_cooked();
819        let length = volume_info.x_length() * volume_info.y_length();
820        crate::ffi::get_heightfield_data(&self.node, part_id, length)
821    }
822
823    pub fn set_heightfield_data(&self, part_id: i32, name: &str, data: &[f32]) -> Result<()> {
824        crate::ffi::set_heightfield_data(&self.node, part_id, &CString::new(name)?, data)
825    }
826
827    pub fn create_heightfield_input(
828        &self,
829        parent: impl Into<Option<NodeHandle>>,
830        volume_name: &str,
831        x_size: i32,
832        y_size: i32,
833        voxel_size: f32,
834        sampling: HeightFieldSampling,
835    ) -> Result<HeightfieldNodes> {
836        let name = CString::new(volume_name)?;
837        let (heightfield, height, mask, merge) = crate::ffi::create_heightfield_input(
838            &self.node,
839            parent.into(),
840            &name,
841            x_size,
842            y_size,
843            voxel_size,
844            sampling,
845        )?;
846        Ok(HeightfieldNodes {
847            heightfield: NodeHandle(heightfield).to_node(&self.node.session)?,
848            height: NodeHandle(height).to_node(&self.node.session)?,
849            mask: NodeHandle(mask).to_node(&self.node.session)?,
850            merge: NodeHandle(merge).to_node(&self.node.session)?,
851        })
852    }
853
854    pub fn create_heightfield_input_volume(
855        &self,
856        parent: impl Into<Option<NodeHandle>>,
857        volume_name: &str,
858        x_size: i32,
859        y_size: i32,
860        voxel_size: f32,
861    ) -> Result<HoudiniNode> {
862        let name = CString::new(volume_name)?;
863        let handle = crate::ffi::create_heightfield_input_volume(
864            &self.node,
865            parent.into(),
866            &name,
867            x_size,
868            y_size,
869            voxel_size,
870        )?;
871        handle.to_node(&self.node.session)
872    }
873}
874
875/// Holds HoudiniNode handles to a heightfield SOP
876/// Used with [`Geometry::create_heightfield_input`]
877pub struct HeightfieldNodes {
878    pub heightfield: HoudiniNode,
879    pub height: HoudiniNode,
880    pub mask: HoudiniNode,
881    pub merge: HoudiniNode,
882}
883
884impl PartInfo {
885    pub fn element_count_by_group(&self, group_type: GroupType) -> i32 {
886        crate::ffi::get_element_count_by_group(self, group_type)
887    }
888}
889
890/// Geometry extension trait with some useful utilities
891pub mod extra {
892    use super::*;
893    pub trait GeometryExtension {
894        fn create_position_attribute(&self, part: &PartInfo) -> Result<NumericAttr<f32>>;
895        fn create_point_color_attribute(&self, part: &PartInfo) -> Result<NumericAttr<f32>>;
896        fn get_color_attribute(
897            &self,
898            part: &PartInfo,
899            owner: AttributeOwner,
900        ) -> Result<Option<NumericAttr<f32>>>;
901        fn get_normal_attribute(
902            &self,
903            part: &PartInfo,
904            owner: AttributeOwner,
905        ) -> Result<Option<NumericAttr<f32>>>;
906        fn get_position_attribute(&self, part: &PartInfo) -> Result<Option<NumericAttr<f32>>>;
907    }
908
909    impl GeometryExtension for Geometry {
910        fn create_position_attribute(&self, part: &PartInfo) -> Result<NumericAttr<f32>> {
911            create_point_tuple_attribute::<3>(self, part, AttributeName::P)
912        }
913
914        fn create_point_color_attribute(&self, part: &PartInfo) -> Result<NumericAttr<f32>> {
915            create_point_tuple_attribute::<3>(self, part, AttributeName::Cd)
916        }
917
918        fn get_color_attribute(
919            &self,
920            part: &PartInfo,
921            owner: AttributeOwner,
922        ) -> Result<Option<NumericAttr<f32>>> {
923            debug_assert!(matches!(
924                owner,
925                AttributeOwner::Point | AttributeOwner::Vertex
926            ));
927            get_tuple3_attribute(self, part, AttributeName::Cd, owner)
928        }
929        fn get_normal_attribute(
930            &self,
931            part: &PartInfo,
932            owner: AttributeOwner,
933        ) -> Result<Option<NumericAttr<f32>>> {
934            debug_assert!(matches!(
935                owner,
936                AttributeOwner::Point | AttributeOwner::Vertex
937            ));
938            get_tuple3_attribute(self, part, AttributeName::N, owner)
939        }
940        fn get_position_attribute(&self, part: &PartInfo) -> Result<Option<NumericAttr<f32>>> {
941            get_tuple3_attribute(self, part, AttributeName::P, AttributeOwner::Point)
942        }
943    }
944
945    #[inline]
946    fn create_point_tuple_attribute<const N: usize>(
947        geo: &Geometry,
948        part: &PartInfo,
949        name: AttributeName,
950    ) -> Result<NumericAttr<f32>> {
951        log::debug!("Creating point attriute {:?}", name);
952        let name: CString = name.into();
953        let attr_info = AttributeInfo::default()
954            .with_count(part.point_count())
955            .with_tuple_size(N as i32)
956            .with_owner(AttributeOwner::Point)
957            .with_storage(StorageType::Float);
958        crate::ffi::add_attribute(&geo.node, part.part_id(), &name, &attr_info.0)
959            .map(|_| NumericAttr::new(name, attr_info, geo.node.clone()))
960    }
961
962    #[inline]
963    fn get_tuple3_attribute(
964        geo: &Geometry,
965        part: &PartInfo,
966        name: AttributeName,
967        owner: AttributeOwner,
968    ) -> Result<Option<NumericAttr<f32>>> {
969        let name: CString = name.into();
970        AttributeInfo::new(&geo.node, part.part_id(), owner, &name).map(|info| {
971            info.exists()
972                .then(|| NumericAttr::new(name, info, geo.node.clone()))
973        })
974    }
975}