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