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