Skip to main content

hapi_rs/
geometry.rs

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