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