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