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