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 fn assert_node_cooked(&self) {
128 debug_assert!(
129 crate::ffi::get_node_info(self.node.handle, &self.node.session)
130 .expect("NodeInfo")
131 .totalCookCount
132 > 0,
133 "Node not cooked"
134 );
135 }
136
137 pub fn volume_bounds(&self, part_id: i32) -> Result<VolumeBounds> {
138 self.assert_node_cooked();
139 crate::ffi::get_volume_bounds(&self.node, part_id)
140 }
141 pub fn get_volume_visual_info(&self, part_id: i32) -> Result<VolumeVisualInfo> {
142 crate::ffi::get_volume_visual_info(&self.node, part_id).map(VolumeVisualInfo)
143 }
144
145 pub fn geo_info(&self) -> Result<GeoInfo> {
148 self.assert_node_cooked();
149 GeoInfo::from_node(&self.node)
150 }
151
152 pub fn set_part_info(&self, info: &PartInfo) -> Result<()> {
153 debug_assert!(self.node.is_valid()?);
154 crate::ffi::set_part_info(&self.node, info)
155 }
156
157 pub fn box_info(&self, part_id: i32) -> Result<BoxInfo> {
158 self.assert_node_cooked();
159 crate::ffi::get_box_info(self.node.handle, &self.node.session, part_id).map(BoxInfo)
160 }
161
162 pub fn sphere_info(&self, part_id: i32) -> Result<SphereInfo> {
163 self.assert_node_cooked();
164 crate::ffi::get_sphere_info(self.node.handle, &self.node.session, part_id).map(SphereInfo)
165 }
166
167 pub fn set_curve_info(&self, part_id: i32, info: &CurveInfo) -> Result<()> {
168 debug_assert!(self.node.is_valid()?);
169 crate::ffi::set_curve_info(&self.node, part_id, info)
170 }
171
172 pub fn set_input_curve_info(&self, part_id: i32, info: &InputCurveInfo) -> Result<()> {
173 debug_assert!(self.node.is_valid()?);
174 crate::ffi::set_input_curve_info(&self.node, part_id, info)
175 }
176
177 pub fn get_input_curve_info(&self, part_id: i32) -> Result<InputCurveInfo> {
178 debug_assert!(self.node.is_valid()?);
179 crate::ffi::get_input_curve_info(&self.node, part_id).map(InputCurveInfo)
180 }
181
182 pub fn set_input_curve_positions(&self, part_id: i32, positions: &[f32]) -> Result<()> {
183 crate::ffi::set_input_curve_positions(
184 &self.node,
185 part_id,
186 positions,
187 0,
188 positions.len() as i32,
189 )
190 }
191
192 pub fn set_input_curve_transform(
193 &self,
194 part_id: i32,
195 positions: &[f32],
196 rotation: &[f32],
197 scale: &[f32],
198 ) -> Result<()> {
199 crate::ffi::set_input_curve_transform(&self.node, part_id, positions, rotation, scale)
200 }
201
202 pub fn set_curve_counts(&self, part_id: i32, count: &[i32]) -> Result<()> {
203 debug_assert!(self.node.is_valid()?);
204 crate::ffi::set_curve_counts(&self.node, part_id, count)
205 }
206
207 pub fn set_curve_knots(&self, part_id: i32, knots: &[f32]) -> Result<()> {
208 debug_assert!(self.node.is_valid()?);
209 crate::ffi::set_curve_knots(&self.node, part_id, knots)
210 }
211
212 pub fn set_vertex_list(&self, part_id: i32, list: impl AsRef<[i32]>) -> Result<()> {
213 debug_assert!(self.node.is_valid()?);
214 crate::ffi::set_geo_vertex_list(&self.node, part_id, list.as_ref())
215 }
216
217 pub fn set_face_counts(&self, part_id: i32, list: impl AsRef<[i32]>) -> Result<()> {
218 debug_assert!(self.node.is_valid()?);
219 crate::ffi::set_geo_face_counts(&self.node, part_id, list.as_ref())
220 }
221
222 pub fn update(&mut self) -> Result<()> {
223 self.info = self.geo_info()?;
224 Ok(())
225 }
226
227 pub fn curve_info(&self, part_id: i32) -> Result<CurveInfo> {
228 self.assert_node_cooked();
229 crate::ffi::get_curve_info(&self.node, part_id).map(CurveInfo)
230 }
231
232 pub fn curve_counts(&self, part_id: i32, start: i32, length: i32) -> Result<Vec<i32>> {
234 self.assert_node_cooked();
235 crate::ffi::get_curve_counts(&self.node, part_id, start, length)
236 }
237
238 pub fn curve_orders(&self, part_id: i32, start: i32, length: i32) -> Result<Vec<i32>> {
240 self.assert_node_cooked();
241 crate::ffi::get_curve_orders(&self.node, part_id, start, length)
242 }
243
244 pub fn curve_knots(&self, part_id: i32, start: i32, length: i32) -> Result<Vec<f32>> {
246 self.assert_node_cooked();
247 crate::ffi::get_curve_knots(&self.node, part_id, start, length)
248 }
249
250 pub fn vertex_list(&self, part: &PartInfo) -> Result<Vec<i32>> {
254 self.assert_node_cooked();
255 crate::ffi::get_geo_vertex_list(
256 &self.node.session,
257 self.node.handle,
258 part.part_id(),
259 0,
260 part.vertex_count(),
261 )
262 }
263
264 pub fn partitions(&self) -> Result<Vec<PartInfo>> {
265 self.assert_node_cooked();
266 (0..self.geo_info()?.part_count())
267 .map(|part| self.part_info(part))
268 .collect()
269 }
270
271 pub fn get_face_counts(&self, part: &PartInfo) -> Result<Vec<i32>> {
272 self.assert_node_cooked();
273 crate::ffi::get_face_counts(
274 &self.node.session,
275 self.node.handle,
276 part.part_id(),
277 0,
278 part.face_count(),
279 )
280 }
281
282 pub fn get_materials(&self, part: &PartInfo) -> Result<Option<Materials>> {
284 self.assert_node_cooked();
285 let (all_the_same, mats) = crate::ffi::get_material_node_ids_on_faces(
286 &self.node.session,
287 self.node.handle,
288 part.face_count(),
289 part.part_id(),
290 )?;
291 if all_the_same {
292 if mats[0] == -1 {
293 Ok(None)
294 } else {
295 let mat_node = NodeHandle(mats[0]);
296 let info = crate::ffi::get_material_info(&self.node.session, mat_node)?;
297 Ok(Some(Materials::Single(Material {
298 session: self.node.session.clone(),
299 info,
300 })))
301 }
302 } else {
303 let session = self.node.session.clone();
304 let mats = mats
305 .into_iter()
306 .map(|id| {
307 crate::ffi::get_material_info(&session, NodeHandle(id)).map(|info| Material {
308 session: session.clone(),
309 info,
310 })
311 })
312 .collect::<Result<Vec<_>>>();
313 mats.map(|vec| Some(Materials::Multiple(vec)))
314 }
315 }
316
317 pub fn get_group_names(&self, group_type: GroupType) -> Result<StringArray> {
319 self.assert_node_cooked();
320 let count = match group_type {
321 GroupType::Point => self.info.point_group_count(),
322 GroupType::Prim => self.info.primitive_group_count(),
323 GroupType::Edge => self.info.edge_group_count(),
324 _ => unreachable!("Impossible GroupType value"),
325 };
326 crate::ffi::get_group_names(&self.node, group_type, count)
327 }
328
329 pub fn get_edge_count_of_edge_group(&self, group: &str, part_id: i32) -> Result<i32> {
330 self.assert_node_cooked();
331 let group = CString::new(group)?;
332 crate::ffi::get_edge_count_of_edge_group(
333 &self.node.session,
334 self.node.handle,
335 &group,
336 part_id,
337 )
338 }
339 pub fn get_element_count_by_owner(
341 &self,
342 part: &PartInfo,
343 owner: AttributeOwner,
344 ) -> Result<i32> {
345 crate::ffi::get_element_count_by_attribute_owner(part, owner)
346 }
347
348 pub fn get_attribute_count_by_owner(
350 &self,
351 part: &PartInfo,
352 owner: AttributeOwner,
353 ) -> Result<i32> {
354 crate::ffi::get_attribute_count_by_owner(part, owner)
355 }
356
357 pub fn get_attribute_names(
358 &self,
359 owner: AttributeOwner,
360 part: &PartInfo,
361 ) -> Result<StringArray> {
362 self.assert_node_cooked();
363 let counts = part.attribute_counts();
364 let count = match owner {
365 AttributeOwner::Invalid => panic!("Invalid AttributeOwner"),
366 AttributeOwner::Vertex => counts[0],
367 AttributeOwner::Point => counts[1],
368 AttributeOwner::Prim => counts[2],
369 AttributeOwner::Detail => counts[3],
370 AttributeOwner::Max => panic!("Invalid AttributeOwner"),
371 };
372 crate::ffi::get_attribute_names(&self.node, part.part_id(), count, owner)
373 }
374
375 pub fn get_position_attribute(&self, part_id: i32) -> Result<NumericAttr<f32>> {
377 self.assert_node_cooked();
378 let name = CString::from(AttributeName::P);
379 let info = AttributeInfo::new(&self.node, part_id, AttributeOwner::Point, name.as_c_str())?;
380 Ok(NumericAttr::new(name, info, self.node.clone()))
381 }
382
383 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 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 panic!("Invalid attribute storage {name:?}: {s:?}")
418 }
419 StorageType::Int => NumericAttr::<i32>::new(name, info, node).boxed(),
420 StorageType::Int64 => NumericAttr::<i64>::new(name, info, node).boxed(),
421 StorageType::Float => NumericAttr::<f32>::new(name, info, node).boxed(),
422 StorageType::Float64 => NumericAttr::<f64>::new(name, info, node).boxed(),
423 StorageType::String => StringAttr::new(name, info, node).boxed(),
424 StorageType::Uint8 => NumericAttr::<u8>::new(name, info, node).boxed(),
425 StorageType::Int8 => NumericAttr::<i8>::new(name, info, node).boxed(),
426 StorageType::Int16 => NumericAttr::<i16>::new(name, info, node).boxed(),
427 StorageType::IntArray => NumericArrayAttr::<i32>::new(name, info, node).boxed(),
428 StorageType::Int64Array => NumericArrayAttr::<i64>::new(name, info, node).boxed(),
429 StorageType::FloatArray => NumericArrayAttr::<f32>::new(name, info, node).boxed(),
430 StorageType::Float64Array => NumericArrayAttr::<f64>::new(name, info, node).boxed(),
431 StorageType::StringArray => StringArrayAttr::new(name, info, node).boxed(),
432 StorageType::Uint8Array => NumericArrayAttr::<u8>::new(name, info, node).boxed(),
433 StorageType::Int8Array => NumericArrayAttr::<i8>::new(name, info, node).boxed(),
434 StorageType::Int16Array => NumericArrayAttr::<i16>::new(name, info, node).boxed(),
435 StorageType::Dictionary => DictionaryAttr::new(name, info, node).boxed(),
436 StorageType::DictionaryArray => DictionaryArrayAttr::new(name, info, node).boxed(),
437 };
438 Ok(Some(Attribute::new(attr_obj)))
439 }
440
441 pub fn add_numeric_attribute<T: AttribAccess>(
443 &self,
444 name: &str,
445 part_id: i32,
446 info: AttributeInfo,
447 ) -> Result<NumericAttr<T>> {
448 debug_assert_eq!(info.storage(), T::storage());
449 debug_assert!(
450 info.tuple_size() > 0,
451 "attribute \"{}\" tuple_size must be > 0",
452 name
453 );
454 log::debug!("Adding numeric geometry attriubute: {name}");
455 let name = CString::new(name)?;
456 crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
457 Ok(NumericAttr::<T>::new(name, info, self.node.clone()))
458 }
459
460 pub fn add_numeric_array_attribute<T>(
462 &self,
463 name: &str,
464 part_id: i32,
465 info: AttributeInfo,
466 ) -> Result<NumericArrayAttr<T>>
467 where
468 T: AttribAccess,
469 [T]: ToOwned<Owned = Vec<T>>,
470 {
471 debug_assert_eq!(info.storage(), T::storage_array());
472 debug_assert!(
473 info.tuple_size() > 0,
474 "AttributeInfo::tuple_size must be 1 for array attributes"
475 );
476 log::debug!("Adding numeric array geometry attriubute: {name}");
477 let name = CString::new(name)?;
478 crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
479 Ok(NumericArrayAttr::<T>::new(name, info, self.node.clone()))
480 }
481
482 pub fn add_string_attribute(
484 &self,
485 name: &str,
486 part_id: i32,
487 info: AttributeInfo,
488 ) -> Result<StringAttr> {
489 debug_assert!(self.node.is_valid()?);
490 debug_assert_eq!(info.storage(), StorageType::String);
491 debug_assert!(
492 info.tuple_size() > 0,
493 "attribute \"{}\" tuple_size must be > 0",
494 name
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 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 \"{}\" tuple_size must be > 0",
514 name
515 );
516 log::debug!("Adding string array geometry attriubute: {name}");
517 let name = CString::new(name)?;
518 crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
519 Ok(StringArrayAttr::new(name, info, self.node.clone()))
520 }
521
522 pub fn add_dictionary_attribute(
524 &self,
525 name: &str,
526 part_id: i32,
527 info: AttributeInfo,
528 ) -> Result<DictionaryAttr> {
529 debug_assert!(self.node.is_valid()?);
530 debug_assert_eq!(info.storage(), StorageType::Dictionary);
531 debug_assert!(
532 info.tuple_size() > 0,
533 "attribute \"{}\" tuple_size must be > 0",
534 name
535 );
536 log::debug!("Adding dictionary geometry attriubute: {name}");
537 let name = CString::new(name)?;
538 crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
539 Ok(DictionaryAttr::new(name, info, self.node.clone()))
540 }
541
542 pub fn add_dictionary_array_attribute(
544 &self,
545 name: &str,
546 part_id: i32,
547 info: AttributeInfo,
548 ) -> Result<DictionaryArrayAttr> {
549 debug_assert!(self.node.is_valid()?);
550 debug_assert_eq!(info.storage(), StorageType::DictionaryArray);
551 debug_assert!(
552 info.tuple_size() > 0,
553 "attribute \"{}\" tuple_size must be > 0",
554 name
555 );
556 log::debug!("Adding dictionary array geometry attriubute: {name}");
557 let name = CString::new(name)?;
558 crate::ffi::add_attribute(&self.node, part_id, &name, &info.0)?;
559 Ok(DictionaryArrayAttr::new(name, info, self.node.clone()))
560 }
561
562 pub fn add_group(
564 &self,
565 part_id: i32,
566 group_type: GroupType,
567 group_name: &str,
568 membership: Option<&[i32]>,
569 ) -> Result<()> {
570 debug_assert!(self.node.is_valid()?);
571 let group_name = CString::new(group_name)?;
572 crate::ffi::add_group(
573 &self.node.session,
574 self.node.handle,
575 part_id,
576 group_type,
577 &group_name,
578 )?;
579 match membership {
580 None => Ok(()),
581 Some(array) => crate::ffi::set_group_membership(
582 &self.node.session,
583 self.node.handle,
584 part_id,
585 group_type,
586 &group_name,
587 array,
588 ),
589 }
590 }
591
592 pub fn delete_group(
594 &self,
595 part_id: i32,
596 group_type: GroupType,
597 group_name: &str,
598 ) -> Result<()> {
599 debug_assert!(self.node.is_valid()?);
600 let group_name = CString::new(group_name)?;
601 crate::ffi::delete_group(
602 &self.node.session,
603 self.node.handle,
604 part_id,
605 group_type,
606 &group_name,
607 )
608 }
609
610 pub fn set_group_membership(
612 &self,
613 part_id: i32,
614 group_type: GroupType,
615 group_name: &str,
616 array: &[i32],
617 ) -> Result<()> {
618 debug_assert!(self.node.is_valid()?);
619 let group_name = CString::new(group_name)?;
620 crate::ffi::set_group_membership(
621 &self.node.session,
622 self.node.handle,
623 part_id,
624 group_type,
625 &group_name,
626 array,
627 )
628 }
629
630 pub fn get_group_membership(
632 &self,
633 part: &PartInfo,
634 group_type: GroupType,
635 group_name: &str,
636 ) -> Result<Vec<i32>> {
637 self.assert_node_cooked();
638 let group_name = CString::new(group_name)?;
639 crate::ffi::get_group_membership(
640 &self.node.session,
641 self.node.handle,
642 part.part_id(),
643 group_type,
644 &group_name,
645 part.element_count_by_group(group_type),
646 )
647 }
648
649 pub fn group_count_by_type(&self, group_type: GroupType) -> Result<i32> {
651 self.assert_node_cooked();
652 Ok(crate::ffi::get_group_count_by_type(&self.info, group_type))
653 }
654
655 pub fn get_instanced_part_ids(&self, part: &PartInfo) -> Result<Vec<i32>> {
656 self.assert_node_cooked();
657 crate::ffi::get_instanced_part_ids(
658 &self.node.session,
659 self.node.handle,
660 part.part_id(),
661 part.instanced_part_count(),
662 )
663 }
664
665 pub fn get_group_membership_on_packed_instance_part(
668 &self,
669 part: &PartInfo,
670 group_type: GroupType,
671 group_name: &CStr,
672 ) -> Result<(bool, Vec<i32>)> {
673 crate::ffi::get_group_membership_on_packed_instance_part(
674 &self.node, part, group_type, group_name,
675 )
676 }
677
678 pub fn get_group_count_on_packed_instance(&self, part: &PartInfo) -> Result<(i32, i32)> {
679 self.assert_node_cooked();
680 crate::ffi::get_group_count_on_instance_part(
681 &self.node.session,
682 self.node.handle,
683 part.part_id(),
684 )
685 }
686
687 pub fn get_instance_part_groups_names(
688 &self,
689 group: GroupType,
690 part_id: i32,
691 ) -> Result<StringArray> {
692 self.assert_node_cooked();
693 crate::ffi::get_group_names_on_instance_part(
694 &self.node.session,
695 self.node.handle,
696 part_id,
697 group,
698 )
699 }
700
701 pub fn get_instance_part_transforms(
702 &self,
703 part: &PartInfo,
704 order: RSTOrder,
705 ) -> Result<Vec<Transform>> {
706 self.assert_node_cooked();
707 crate::ffi::get_instanced_part_transforms(
708 &self.node.session,
709 self.node.handle,
710 part.part_id(),
711 order,
712 part.instance_count(),
713 )
714 .map(|vec| vec.into_iter().map(Transform).collect())
715 }
716
717 pub fn save_to_file(&self, filepath: &str) -> Result<()> {
719 self.assert_node_cooked();
720 let path = CString::new(filepath)?;
721 crate::ffi::save_geo_to_file(&self.node, &path)
722 }
723
724 pub fn load_from_file(&self, filepath: &str) -> Result<()> {
726 debug_assert!(self.node.is_valid()?);
727 let path = CString::new(filepath)?;
728 crate::ffi::load_geo_from_file(&self.node, &path)
729 }
730
731 pub fn commit(&self) -> Result<()> {
733 debug_assert!(self.node.is_valid()?);
734 log::debug!("Commiting geometry changes");
735 crate::ffi::commit_geo(&self.node)
736 }
737
738 pub fn revert(&self) -> Result<()> {
740 debug_assert!(self.node.is_valid()?);
741 crate::ffi::revert_geo(&self.node)
742 }
743
744 pub fn save_to_memory(&self, format: GeoFormat) -> Result<Vec<i8>> {
746 self.assert_node_cooked();
747 crate::ffi::save_geo_to_memory(&self.node.session, self.node.handle, format.as_cstr())
748 }
749
750 pub fn load_from_memory(&self, data: &[i8], format: GeoFormat) -> Result<()> {
752 crate::ffi::load_geo_from_memory(
753 &self.node.session,
754 self.node.handle,
755 data,
756 format.as_cstr(),
757 )
758 }
759
760 pub fn read_volume_tile<T: VolumeStorage>(
761 &self,
762 part: i32,
763 fill: T,
764 tile: &VolumeTileInfo,
765 values: &mut [T],
766 ) -> Result<()> {
767 self.assert_node_cooked();
768 T::read_tile(&self.node, part, fill, values, &tile.0)
769 }
770
771 pub fn write_volume_tile<T: VolumeStorage>(
772 &self,
773 part: i32,
774 tile: &VolumeTileInfo,
775 values: &[T],
776 ) -> Result<()> {
777 self.assert_node_cooked();
778 T::write_tile(&self.node, part, values, &tile.0)
779 }
780
781 pub fn read_volume_voxel<T: VolumeStorage>(
782 &self,
783 part: i32,
784 x_index: i32,
785 y_index: i32,
786 z_index: i32,
787 values: &mut [T],
788 ) -> Result<()> {
789 self.assert_node_cooked();
790 T::read_voxel(&self.node, part, x_index, y_index, z_index, values)
791 }
792
793 pub fn write_volume_voxel<T: VolumeStorage>(
794 &self,
795 part: i32,
796 x_index: i32,
797 y_index: i32,
798 z_index: i32,
799 values: &[T],
800 ) -> Result<()> {
801 self.assert_node_cooked();
802 T::write_voxel(&self.node, part, x_index, y_index, z_index, values)
803 }
804
805 pub fn foreach_volume_tile(
807 &self,
808 part: i32,
809 info: &VolumeInfo,
810 callback: impl Fn(Tile),
811 ) -> Result<()> {
812 self.assert_node_cooked();
813 let tile_size = (info.tile_size().pow(3) * info.tuple_size()) as usize;
814 crate::volume::iterate_tiles(&self.node, part, tile_size, callback)
815 }
816
817 pub fn get_heightfield_data(&self, part_id: i32, volume_info: &VolumeInfo) -> Result<Vec<f32>> {
818 self.assert_node_cooked();
819 let length = volume_info.x_length() * volume_info.y_length();
820 crate::ffi::get_heightfield_data(&self.node, part_id, length)
821 }
822
823 pub fn set_heightfield_data(&self, part_id: i32, name: &str, data: &[f32]) -> Result<()> {
824 crate::ffi::set_heightfield_data(&self.node, part_id, &CString::new(name)?, data)
825 }
826
827 pub fn create_heightfield_input(
828 &self,
829 parent: impl Into<Option<NodeHandle>>,
830 volume_name: &str,
831 x_size: i32,
832 y_size: i32,
833 voxel_size: f32,
834 sampling: HeightFieldSampling,
835 ) -> Result<HeightfieldNodes> {
836 let name = CString::new(volume_name)?;
837 let (heightfield, height, mask, merge) = crate::ffi::create_heightfield_input(
838 &self.node,
839 parent.into(),
840 &name,
841 x_size,
842 y_size,
843 voxel_size,
844 sampling,
845 )?;
846 Ok(HeightfieldNodes {
847 heightfield: NodeHandle(heightfield).to_node(&self.node.session)?,
848 height: NodeHandle(height).to_node(&self.node.session)?,
849 mask: NodeHandle(mask).to_node(&self.node.session)?,
850 merge: NodeHandle(merge).to_node(&self.node.session)?,
851 })
852 }
853
854 pub fn create_heightfield_input_volume(
855 &self,
856 parent: impl Into<Option<NodeHandle>>,
857 volume_name: &str,
858 x_size: i32,
859 y_size: i32,
860 voxel_size: f32,
861 ) -> Result<HoudiniNode> {
862 let name = CString::new(volume_name)?;
863 let handle = crate::ffi::create_heightfield_input_volume(
864 &self.node,
865 parent.into(),
866 &name,
867 x_size,
868 y_size,
869 voxel_size,
870 )?;
871 handle.to_node(&self.node.session)
872 }
873}
874
875pub struct HeightfieldNodes {
878 pub heightfield: HoudiniNode,
879 pub height: HoudiniNode,
880 pub mask: HoudiniNode,
881 pub merge: HoudiniNode,
882}
883
884impl PartInfo {
885 pub fn element_count_by_group(&self, group_type: GroupType) -> i32 {
886 crate::ffi::get_element_count_by_group(self, group_type)
887 }
888}
889
890pub mod extra {
892 use super::*;
893 pub trait GeometryExtension {
894 fn create_position_attribute(&self, part: &PartInfo) -> Result<NumericAttr<f32>>;
895 fn create_point_color_attribute(&self, part: &PartInfo) -> Result<NumericAttr<f32>>;
896 fn get_color_attribute(
897 &self,
898 part: &PartInfo,
899 owner: AttributeOwner,
900 ) -> Result<Option<NumericAttr<f32>>>;
901 fn get_normal_attribute(
902 &self,
903 part: &PartInfo,
904 owner: AttributeOwner,
905 ) -> Result<Option<NumericAttr<f32>>>;
906 fn get_position_attribute(&self, part: &PartInfo) -> Result<Option<NumericAttr<f32>>>;
907 }
908
909 impl GeometryExtension for Geometry {
910 fn create_position_attribute(&self, part: &PartInfo) -> Result<NumericAttr<f32>> {
911 create_point_tuple_attribute::<3>(self, part, AttributeName::P)
912 }
913
914 fn create_point_color_attribute(&self, part: &PartInfo) -> Result<NumericAttr<f32>> {
915 create_point_tuple_attribute::<3>(self, part, AttributeName::Cd)
916 }
917
918 fn get_color_attribute(
919 &self,
920 part: &PartInfo,
921 owner: AttributeOwner,
922 ) -> Result<Option<NumericAttr<f32>>> {
923 debug_assert!(matches!(
924 owner,
925 AttributeOwner::Point | AttributeOwner::Vertex
926 ));
927 get_tuple3_attribute(self, part, AttributeName::Cd, owner)
928 }
929 fn get_normal_attribute(
930 &self,
931 part: &PartInfo,
932 owner: AttributeOwner,
933 ) -> Result<Option<NumericAttr<f32>>> {
934 debug_assert!(matches!(
935 owner,
936 AttributeOwner::Point | AttributeOwner::Vertex
937 ));
938 get_tuple3_attribute(self, part, AttributeName::N, owner)
939 }
940 fn get_position_attribute(&self, part: &PartInfo) -> Result<Option<NumericAttr<f32>>> {
941 get_tuple3_attribute(self, part, AttributeName::P, AttributeOwner::Point)
942 }
943 }
944
945 #[inline]
946 fn create_point_tuple_attribute<const N: usize>(
947 geo: &Geometry,
948 part: &PartInfo,
949 name: AttributeName,
950 ) -> Result<NumericAttr<f32>> {
951 log::debug!("Creating point attriute {:?}", name);
952 let name: CString = name.into();
953 let attr_info = AttributeInfo::default()
954 .with_count(part.point_count())
955 .with_tuple_size(N as i32)
956 .with_owner(AttributeOwner::Point)
957 .with_storage(StorageType::Float);
958 crate::ffi::add_attribute(&geo.node, part.part_id(), &name, &attr_info.0)
959 .map(|_| NumericAttr::new(name, attr_info, geo.node.clone()))
960 }
961
962 #[inline]
963 fn get_tuple3_attribute(
964 geo: &Geometry,
965 part: &PartInfo,
966 name: AttributeName,
967 owner: AttributeOwner,
968 ) -> Result<Option<NumericAttr<f32>>> {
969 let name: CString = name.into();
970 AttributeInfo::new(&geo.node, part.part_id(), owner, &name).map(|info| {
971 info.exists()
972 .then(|| NumericAttr::new(name, info, geo.node.clone()))
973 })
974 }
975}