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