hapi_rs/attribute/
mod.rs

1//! Geometry attributes access and iterators
2//!
3//! Geometry attributes of different types are represented as trait objects
4//! and need to be downcast to concrete types
5//!
6//! ```
7//!
8//! use hapi_rs::session::new_in_process;
9//! use hapi_rs::geometry::*;
10//! use hapi_rs::attribute::*;
11//! let session = new_in_process(None).unwrap();
12//! let lib = session.load_asset_file("../otls/hapi_geo.hda").unwrap();
13//! let node = lib.try_create_first().unwrap();
14//! let geo = node.geometry().unwrap().unwrap();
15//! geo.node.cook_blocking().unwrap();
16//! let attr_p = geo.get_attribute(0, AttributeOwner::Point, "P").unwrap().expect("P exists");
17//! let attr_p = attr_p.downcast::<NumericAttr<f32>>().unwrap();
18//! attr_p.get(0).expect("read_attribute");
19//!
20//! ```
21mod array;
22mod async_;
23mod bindings;
24
25use crate::errors::{ErrorContext, Result};
26pub use crate::ffi::AttributeInfo;
27pub use crate::ffi::enums::StorageType;
28use crate::node::HoudiniNode;
29use crate::stringhandle::{StringArray, StringHandle};
30pub use array::*;
31use async_::AsyncAttribResult;
32use std::any::Any;
33use std::borrow::Cow;
34use std::ffi::{CStr, CString};
35use std::marker::PhantomData;
36
37pub type JobId = i32;
38
39mod private {
40    pub trait Sealed {}
41}
42pub trait AttribAccess: private::Sealed + Clone + Default + Send + Sized + 'static {
43    fn storage() -> StorageType;
44    fn storage_array() -> StorageType;
45    fn get(
46        name: &CStr,
47        node: &HoudiniNode,
48        info: &AttributeInfo,
49        part_id: i32,
50        buffer: &mut Vec<Self>,
51    ) -> Result<()>;
52    fn get_async(
53        name: &CStr,
54        node: &HoudiniNode,
55        info: &AttributeInfo,
56        part_id: i32,
57        buffer: &mut Vec<Self>,
58    ) -> Result<JobId>;
59    fn set(
60        name: &CStr,
61        node: &HoudiniNode,
62        info: &AttributeInfo,
63        part_id: i32,
64        data: &[Self],
65        start: i32,
66        len: i32,
67    ) -> Result<()>;
68
69    fn set_async(
70        name: &CStr,
71        node: &HoudiniNode,
72        info: &AttributeInfo,
73        part: i32,
74        data: &[Self],
75        start: i32,
76        len: i32,
77    ) -> Result<JobId>;
78
79    fn set_unique(
80        name: &CStr,
81        node: &HoudiniNode,
82        info: &AttributeInfo,
83        part_id: i32,
84        data: &[Self],
85        start: i32,
86    ) -> Result<()>;
87
88    fn set_unique_async(
89        name: &CStr,
90        node: &HoudiniNode,
91        info: &AttributeInfo,
92        part_id: i32,
93        data: &[Self],
94        start: i32,
95    ) -> Result<JobId>;
96
97    fn get_array(
98        name: &CStr,
99        node: &HoudiniNode,
100        info: &AttributeInfo,
101        part: i32,
102    ) -> Result<DataArray<'static, Self>>
103    where
104        [Self]: ToOwned<Owned = Vec<Self>>;
105
106    fn get_array_async(
107        name: &CStr,
108        node: &HoudiniNode,
109        info: &AttributeInfo,
110        data: &mut [Self],
111        sizes: &mut [i32],
112        part: i32,
113    ) -> Result<JobId>;
114    fn set_array(
115        name: &CStr,
116        node: &HoudiniNode,
117        info: &AttributeInfo,
118        part: i32,
119        data: &[Self],
120        sizes: &[i32],
121    ) -> Result<()>
122    where
123        [Self]: ToOwned<Owned = Vec<Self>>;
124
125    fn set_array_async(
126        name: &CStr,
127        node: &HoudiniNode,
128        info: &AttributeInfo,
129        part: i32,
130        data: &[Self],
131        sizes: &[i32],
132    ) -> Result<JobId>;
133}
134
135macro_rules! impl_sealed {
136    ($($x:ident),+ $(,)?) => {
137        $(impl private::Sealed for $x {})+
138    }
139}
140
141impl_sealed!(u8, i8, i16, i32, i64, f32, f64);
142
143impl StorageType {
144    // Helper for matching array types to actual data type,
145    // e.g. StorageType::Array is actually an array of StorageType::Int,
146    // StorageType::FloatArray is StorageType::Float
147    pub(crate) fn type_matches(&self, other: StorageType) -> bool {
148        use StorageType::*;
149        match other {
150            IntArray | Uint8Array | Int8Array | Int16Array | Int64Array => {
151                matches!(*self, Int | Uint8 | Int16 | Int64)
152            }
153            FloatArray | Float64Array => matches!(*self, Float | Float64),
154            StringArray => matches!(*self, StringArray),
155            _st => matches!(*self, _st),
156        }
157    }
158}
159
160pub(crate) struct AttributeBundle {
161    pub(crate) info: AttributeInfo,
162    pub(crate) name: CString,
163    pub(crate) node: HoudiniNode,
164}
165
166pub struct NumericAttr<T: AttribAccess>(pub(crate) AttributeBundle, PhantomData<T>);
167
168pub struct NumericArrayAttr<T: AttribAccess>(pub(crate) AttributeBundle, PhantomData<T>);
169
170pub struct StringAttr(pub(crate) AttributeBundle);
171
172pub struct StringArrayAttr(pub(crate) AttributeBundle);
173
174pub struct DictionaryAttr(pub(crate) AttributeBundle);
175
176pub struct DictionaryArrayAttr(pub(crate) AttributeBundle);
177
178impl<T: AttribAccess> NumericArrayAttr<T>
179where
180    [T]: ToOwned<Owned = Vec<T>>,
181{
182    pub(crate) fn new(
183        name: CString,
184        info: AttributeInfo,
185        node: HoudiniNode,
186    ) -> NumericArrayAttr<T> {
187        NumericArrayAttr(AttributeBundle { info, name, node }, PhantomData)
188    }
189    pub fn get(&self, part_id: i32) -> Result<DataArray<'_, T>> {
190        debug_assert!(self.0.info.storage().type_matches(T::storage()));
191        T::get_array(&self.0.name, &self.0.node, &self.0.info, part_id)
192    }
193
194    pub fn get_async(&self, part_id: i32) -> Result<(JobId, DataArray<'_, T>)> {
195        let info = &self.0.info;
196        debug_assert!(info.storage().type_matches(T::storage()));
197        let mut data = vec![T::default(); info.total_array_elements() as usize];
198        let mut sizes = vec![0i32; info.count() as usize];
199        let job_id = T::get_array_async(
200            &self.0.name,
201            &self.0.node,
202            info,
203            &mut data,
204            &mut sizes,
205            part_id,
206        )?;
207        Ok((job_id, DataArray::new_owned(data, sizes)))
208    }
209
210    pub fn set(&self, part_id: i32, values: &DataArray<T>) -> Result<()> {
211        debug_assert!(self.0.info.storage().type_matches(T::storage()));
212        debug_assert_eq!(
213            self.0.info.count(),
214            values.sizes().len() as i32,
215            "sizes array must be the same as AttributeInfo::count"
216        );
217        debug_assert_eq!(
218            self.0.info.total_array_elements(),
219            values.data().len() as i64,
220            "data array must be the same as AttributeInfo::total_array_elements"
221        );
222        T::set_array(
223            &self.0.name,
224            &self.0.node,
225            &self.0.info,
226            part_id,
227            values.data(),
228            values.sizes(),
229        )
230    }
231}
232
233impl<T: AttribAccess> NumericAttr<T> {
234    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> NumericAttr<T> {
235        NumericAttr(AttributeBundle { info, name, node }, PhantomData)
236    }
237    /// Get attribute value. Allocates a new vector on every call
238    pub fn get(&self, part_id: i32) -> Result<Vec<T>> {
239        debug_assert_eq!(self.0.info.storage(), T::storage());
240        let mut buffer = vec![];
241        T::get(
242            &self.0.name,
243            &self.0.node,
244            &self.0.info,
245            part_id,
246            &mut buffer,
247        )?;
248        Ok(buffer)
249    }
250    /// Start filling a given buffer asynchronously and return a job id.
251    /// It's important to keep the buffer alive until the job is complete
252    pub fn read_async_into(&self, part_id: i32, buffer: &mut Vec<T>) -> Result<i32> {
253        // TODO: Get an updated attribute info since point count can change between calls.
254        // but there's looks like some use after free on the C side, when AttributeInfo gets
255        // accessed after it gets dropped in Rust, so we can't get new AttributeInfo here.
256        let info = &self.0.info;
257        buffer.resize((info.count() * info.tuple_size()) as usize, T::default());
258        T::get_async(&self.0.name, &self.0.node, info, part_id, buffer).with_context(|| {
259            format!(
260                "Reading attribute \"{}\" asynchronously, into buffer size {}",
261                self.0.name.to_string_lossy(),
262                buffer.len()
263            )
264        })
265    }
266
267    pub fn get_async(&self, part_id: i32) -> Result<AsyncAttribResult<T>> {
268        let info = &self.0.info;
269        let size = (info.count() * info.tuple_size()) as usize;
270        let mut data = Vec::<T>::with_capacity(size);
271        let job_id = T::get_async(&self.0.name, &self.0.node, info, part_id, &mut data)
272            .with_context(|| {
273                format!(
274                    "Reading attribute {} asynchronously",
275                    self.0.name.to_string_lossy()
276                )
277            })?;
278        Ok(AsyncAttribResult {
279            job_id,
280            data,
281            size,
282            session: self.0.node.session.clone(),
283        })
284    }
285
286    /// Read the attribute data into a provided buffer. The buffer will be auto-resized
287    /// from the attribute info.
288    pub fn read_into(&self, part_id: i32, buffer: &mut Vec<T>) -> Result<()> {
289        debug_assert_eq!(self.0.info.storage(), T::storage());
290        // Get an updated attribute info since point count can change between calls
291        let info = AttributeInfo::new(&self.0.node, part_id, self.0.info.owner(), &self.0.name)?;
292        T::get(&self.0.name, &self.0.node, &info, part_id, buffer)
293    }
294    pub fn set(&self, part_id: i32, values: &[T]) -> Result<()> {
295        debug_assert_eq!(self.0.info.storage(), T::storage());
296        T::set(
297            &self.0.name,
298            &self.0.node,
299            &self.0.info,
300            part_id,
301            values,
302            0,
303            self.0.info.count().min(values.len() as i32),
304        )
305    }
306
307    /// Set multiple attribute data to the same value.
308    /// value length must be less or equal to attribute tuple size.
309    pub fn set_unique(&self, part_id: i32, value: &[T]) -> Result<()> {
310        debug_assert_eq!(self.0.info.storage(), T::storage());
311        debug_assert!(value.len() <= self.0.info.tuple_size() as usize);
312        T::set_unique(&self.0.name, &self.0.node, &self.0.info, part_id, value, 0)
313    }
314}
315
316impl StringAttr {
317    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> StringAttr {
318        StringAttr(AttributeBundle { info, name, node })
319    }
320    pub fn get(&self, part_id: i32) -> Result<StringArray> {
321        debug_assert!(self.0.node.is_valid()?);
322        bindings::get_attribute_string_data(
323            &self.0.node,
324            part_id,
325            self.0.name.as_c_str(),
326            &self.0.info.0,
327        )
328    }
329
330    pub fn get_async(&self, part_id: i32) -> Result<AsyncAttribResult<StringHandle>> {
331        bindings::get_attribute_string_data_async(
332            &self.0.node,
333            part_id,
334            self.0.name.as_c_str(),
335            &self.0.info.0,
336        )
337    }
338
339    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<()> {
340        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
341        bindings::set_attribute_string_data(
342            &self.0.node,
343            part_id,
344            self.0.name.as_c_str(),
345            &self.0.info.0,
346            ptrs.as_mut(),
347        )
348    }
349
350    pub fn set_async(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<JobId> {
351        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
352        bindings::set_attribute_string_data_async(
353            &self.0.node,
354            self.0.name.as_c_str(),
355            part_id,
356            &self.0.info.0,
357            ptrs.as_mut(),
358        )
359    }
360    /// Set multiple attribute data to the same value.
361    /// value length must be less or equal to attribute tuple size.
362    pub fn set_unique(&self, part: i32, value: &CStr) -> Result<()> {
363        bindings::set_attribute_string_unique_data(
364            &self.0.node,
365            self.0.name.as_c_str(),
366            &self.0.info.0,
367            part,
368            value.as_ptr(),
369        )
370    }
371
372    /// Set multiple attribute string data to the same unique value asynchronously.
373    pub fn set_unique_async(&self, part: i32, value: &CStr) -> Result<JobId> {
374        bindings::set_attribute_string_unique_data_async(
375            &self.0.node,
376            self.0.name.as_c_str(),
377            &self.0.info.0,
378            part,
379            value.as_ptr(),
380        )
381    }
382
383    /// Set attribute string data by index.
384    pub fn set_indexed(
385        &self,
386        part_id: i32,
387        values: &[impl AsRef<CStr>],
388        indices: &[i32],
389    ) -> Result<()> {
390        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
391        bindings::set_attribute_indexed_string_data(
392            &self.0.node,
393            part_id,
394            self.0.name.as_c_str(),
395            &self.0.info.0,
396            ptrs.as_mut(),
397            indices,
398        )
399    }
400
401    pub fn set_indexed_async(
402        &self,
403        part_id: i32,
404        values: &[impl AsRef<CStr>],
405        indices: &[i32],
406    ) -> Result<JobId> {
407        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
408        bindings::set_attribute_indexed_string_data_async(
409            &self.0.node,
410            part_id,
411            self.0.name.as_c_str(),
412            &self.0.info.0,
413            ptrs.as_mut(),
414            indices,
415        )
416    }
417}
418
419impl StringArrayAttr {
420    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> StringArrayAttr {
421        StringArrayAttr(AttributeBundle { info, name, node })
422    }
423    pub fn get(&self, part_id: i32) -> Result<StringMultiArray> {
424        debug_assert!(self.0.node.is_valid()?);
425        bindings::get_attribute_string_array_data(
426            &self.0.node,
427            self.0.name.as_c_str(),
428            part_id,
429            &self.0.info,
430        )
431    }
432
433    pub fn get_async(&self, part_id: i32) -> Result<(JobId, StringMultiArray)> {
434        let mut handles = vec![StringHandle(-1); self.0.info.total_array_elements() as usize];
435        let mut sizes = vec![0; self.0.info.count() as usize];
436        let job_id = bindings::get_attribute_string_array_data_async(
437            &self.0.node,
438            self.0.name.as_c_str(),
439            part_id,
440            &self.0.info.0,
441            &mut handles,
442            &mut sizes,
443        )?;
444        Ok((
445            job_id,
446            StringMultiArray {
447                handles,
448                sizes,
449                session: self.0.node.session.clone(),
450            },
451        ))
452    }
453    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>], sizes: &[i32]) -> Result<()> {
454        debug_assert!(self.0.node.is_valid()?);
455        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
456        bindings::set_attribute_string_array_data(
457            &self.0.node,
458            self.0.name.as_c_str(),
459            &self.0.info.0,
460            part_id,
461            ptrs.as_mut(),
462            sizes,
463        )
464    }
465
466    pub fn set_async(
467        &self,
468        part_id: i32,
469        values: &[impl AsRef<CStr>],
470        sizes: &[i32],
471    ) -> Result<JobId> {
472        debug_assert!(self.0.node.is_valid()?);
473        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
474        bindings::set_attribute_string_array_data_async(
475            &self.0.node,
476            self.0.name.as_c_str(),
477            part_id,
478            &self.0.info.0,
479            ptrs.as_mut(),
480            sizes,
481        )
482    }
483}
484
485impl DictionaryAttr {
486    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> Self {
487        DictionaryAttr(AttributeBundle { info, name, node })
488    }
489
490    pub fn get(&self, part_id: i32) -> Result<StringArray> {
491        debug_assert!(self.0.node.is_valid()?);
492        bindings::get_attribute_dictionary_data(
493            &self.0.node,
494            part_id,
495            self.0.name.as_c_str(),
496            &self.0.info.0,
497        )
498    }
499
500    pub fn set_async(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<JobId> {
501        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
502        bindings::set_attribute_dictionary_data_async(
503            &self.0.node,
504            self.0.name.as_c_str(),
505            part_id,
506            &self.0.info.0,
507            ptrs.as_mut(),
508        )
509    }
510
511    pub fn get_async(&self, part_id: i32) -> Result<AsyncAttribResult<StringHandle>> {
512        bindings::get_attribute_dictionary_data_async(
513            &self.0.node,
514            part_id,
515            self.0.name.as_c_str(),
516            &self.0.info.0,
517        )
518    }
519
520    /// Set dictionary attribute values where each string should be a JSON-encoded value.
521    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<()> {
522        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
523        bindings::set_attribute_dictionary_data(
524            &self.0.node,
525            part_id,
526            self.0.name.as_c_str(),
527            &self.0.info.0,
528            ptrs.as_mut(),
529        )
530    }
531}
532
533impl DictionaryArrayAttr {
534    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> Self {
535        DictionaryArrayAttr(AttributeBundle { info, name, node })
536    }
537    pub fn get(&self, part_id: i32) -> Result<StringMultiArray> {
538        debug_assert!(self.0.node.is_valid()?);
539        bindings::get_attribute_dictionary_array_data(
540            &self.0.node,
541            &self.0.name,
542            part_id,
543            &self.0.info,
544        )
545    }
546
547    pub fn get_async(&self, part_id: i32) -> Result<(JobId, StringMultiArray)> {
548        let mut handles = vec![StringHandle(-1); self.0.info.total_array_elements() as usize];
549        let mut sizes = vec![0; self.0.info.count() as usize];
550        let job_id = bindings::get_attribute_dictionary_array_data_async(
551            &self.0.node,
552            self.0.name.as_c_str(),
553            part_id,
554            &self.0.info.0,
555            &mut handles,
556            &mut sizes,
557        )?;
558        Ok((
559            job_id,
560            StringMultiArray {
561                handles,
562                sizes,
563                session: self.0.node.session.clone(),
564            },
565        ))
566    }
567    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>], sizes: &[i32]) -> Result<()> {
568        debug_assert!(self.0.node.is_valid()?);
569        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
570        bindings::set_attribute_dictionary_array_data(
571            &self.0.node,
572            self.0.name.as_c_str(),
573            &self.0.info.0,
574            part_id,
575            ptrs.as_mut(),
576            sizes,
577        )
578    }
579
580    pub fn set_async(
581        &self,
582        part_id: i32,
583        values: &[impl AsRef<CStr>],
584        sizes: &[i32],
585    ) -> Result<JobId> {
586        debug_assert!(self.0.node.is_valid()?);
587        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
588        bindings::set_attribute_dictionary_array_data_async(
589            &self.0.node,
590            self.0.name.as_c_str(),
591            part_id,
592            &self.0.info.0,
593            ptrs.as_mut(),
594            sizes,
595        )
596    }
597}
598
599#[doc(hidden)]
600pub trait AsAttribute: Send {
601    fn info(&self) -> &AttributeInfo;
602    fn storage(&self) -> StorageType;
603    fn boxed(self) -> Box<dyn AnyAttribWrapper>
604    where
605        Self: Sized + 'static,
606    {
607        Box::new(self)
608    }
609    fn name(&self) -> &CStr;
610    fn node(&self) -> &HoudiniNode;
611}
612
613impl<T: AttribAccess> AsAttribute for NumericAttr<T> {
614    fn info(&self) -> &AttributeInfo {
615        &self.0.info
616    }
617    fn storage(&self) -> StorageType {
618        T::storage()
619    }
620
621    fn name(&self) -> &CStr {
622        &self.0.name
623    }
624
625    fn node(&self) -> &HoudiniNode {
626        &self.0.node
627    }
628}
629
630impl<T: AttribAccess> AsAttribute for NumericArrayAttr<T> {
631    fn info(&self) -> &AttributeInfo {
632        &self.0.info
633    }
634    fn storage(&self) -> StorageType {
635        T::storage()
636    }
637    fn name(&self) -> &CStr {
638        &self.0.name
639    }
640    fn node(&self) -> &HoudiniNode {
641        &self.0.node
642    }
643}
644
645impl AsAttribute for StringAttr {
646    fn info(&self) -> &AttributeInfo {
647        &self.0.info
648    }
649
650    fn storage(&self) -> StorageType {
651        StorageType::String
652    }
653
654    fn name(&self) -> &CStr {
655        &self.0.name
656    }
657
658    fn node(&self) -> &HoudiniNode {
659        &self.0.node
660    }
661}
662
663impl AsAttribute for StringArrayAttr {
664    fn info(&self) -> &AttributeInfo {
665        &self.0.info
666    }
667
668    fn storage(&self) -> StorageType {
669        StorageType::StringArray
670    }
671
672    fn name(&self) -> &CStr {
673        &self.0.name
674    }
675
676    fn node(&self) -> &HoudiniNode {
677        &self.0.node
678    }
679}
680
681impl AsAttribute for DictionaryAttr {
682    fn info(&self) -> &AttributeInfo {
683        &self.0.info
684    }
685
686    fn storage(&self) -> StorageType {
687        StorageType::Dictionary
688    }
689
690    fn name(&self) -> &CStr {
691        &self.0.name
692    }
693
694    fn node(&self) -> &HoudiniNode {
695        &self.0.node
696    }
697}
698
699impl AsAttribute for DictionaryArrayAttr {
700    fn info(&self) -> &AttributeInfo {
701        &self.0.info
702    }
703
704    fn storage(&self) -> StorageType {
705        StorageType::DictionaryArray
706    }
707
708    fn name(&self) -> &CStr {
709        &self.0.name
710    }
711
712    fn node(&self) -> &HoudiniNode {
713        &self.0.node
714    }
715}
716
717#[doc(hidden)]
718pub trait AnyAttribWrapper: Any + AsAttribute {
719    fn as_any(&self) -> &dyn Any;
720}
721
722impl<T: AsAttribute + 'static> AnyAttribWrapper for T {
723    fn as_any(&self) -> &dyn Any {
724        self
725    }
726}
727
728pub struct Attribute(Box<dyn AnyAttribWrapper>);
729
730impl Attribute {
731    pub(crate) fn new(attr_obj: Box<dyn AnyAttribWrapper>) -> Self {
732        Attribute(attr_obj)
733    }
734    pub fn downcast<T: AnyAttribWrapper>(&self) -> Option<&T> {
735        self.0.as_any().downcast_ref::<T>()
736    }
737    pub fn name(&self) -> Cow<'_, str> {
738        self.0.name().to_string_lossy()
739    }
740    pub fn storage(&self) -> StorageType {
741        self.0.storage()
742    }
743    pub fn info(&self) -> &AttributeInfo {
744        self.0.info()
745    }
746    pub fn delete(self, part_id: i32) -> Result<()> {
747        crate::ffi::delete_attribute(self.0.node(), part_id, self.0.name(), &self.0.info().0)
748    }
749}