1use crate::ffi::raw as ffi;
4use crate::ffi::raw::{ChoiceListType, ParmType};
5use crate::node::ManagerType;
6use crate::{
7 HapiError, errors::Result, ffi::ParmChoiceInfo, ffi::ParmInfo, node::HoudiniNode,
8 session::Session,
9};
10use log::debug;
11use std::ffi::{CStr, CString};
12use std::path::PathBuf;
13
14struct AssetParmValues {
15 int: Vec<i32>,
16 float: Vec<f32>,
17 string: Vec<String>,
18 menus: Vec<ParmChoiceInfo>,
19}
20
21pub struct AssetParameters {
24 asset_name: CString,
25 library: AssetLibrary,
26 infos: Vec<ParmInfo>,
27 values: AssetParmValues,
28}
29
30impl<'a> IntoIterator for &'a AssetParameters {
31 type Item = AssetParm<'a>;
32 type IntoIter = AssetParmIter<'a>;
33
34 fn into_iter(self) -> Self::IntoIter {
35 AssetParmIter {
36 library_id: self.library.lib_id,
37 asset_name: &self.asset_name,
38 iter: self.infos.iter(),
39 values: &self.values,
40 }
41 }
42}
43impl<'a> AssetParameters {
44 #[must_use]
45 pub fn iter(&'a self) -> AssetParmIter<'a> {
46 <&Self as IntoIterator>::into_iter(self)
47 }
48}
49
50impl AssetParameters {
51 #[must_use]
53 pub fn find_parameter(&self, name: &str) -> Option<AssetParm<'_>> {
54 self.infos
55 .iter()
56 .find(|p| p.name().is_ok_and(|n| n == name))
57 .map(|info| AssetParm {
58 library_id: self.library.lib_id,
59 asset_name: &self.asset_name,
60 info,
61 values: &self.values,
62 })
63 }
64}
65
66pub struct AssetParmIter<'a> {
68 library_id: i32,
69 asset_name: &'a CStr,
70 iter: std::slice::Iter<'a, ParmInfo>,
71 values: &'a AssetParmValues,
72}
73
74impl<'a> Iterator for AssetParmIter<'a> {
75 type Item = AssetParm<'a>;
76
77 fn next(&mut self) -> Option<Self::Item> {
78 self.iter.next().map(|info| AssetParm {
79 library_id: self.library_id,
80 asset_name: self.asset_name,
81 info,
82 values: self.values,
83 })
84 }
85}
86
87pub struct AssetParm<'a> {
89 library_id: i32,
90 asset_name: &'a CStr,
91 info: &'a ParmInfo,
92 values: &'a AssetParmValues,
93}
94
95impl std::ops::Deref for AssetParm<'_> {
96 type Target = ParmInfo;
97
98 fn deref(&self) -> &Self::Target {
99 self.info
100 }
101}
102
103#[derive(Debug)]
105pub enum ParmValue<'a> {
106 Int(&'a [i32]),
107 Float(&'a [f32]),
108 String(&'a [String]),
109 Toggle(bool),
110 NoDefault,
111}
112
113impl<'a> AssetParm<'a> {
114 #[must_use]
116 pub fn default_value(&self) -> ParmValue<'a> {
117 use ParmType::{
118 Button, Color, Float, Int, Node, PathFile, PathFileDir, PathFileGeo, PathFileImage,
119 String, Toggle,
120 };
121 let size = self.info.size() as usize;
122 match self.info.parm_type() {
123 Int | Button => {
124 let start = self.info.int_values_index() as usize;
125 ParmValue::Int(&self.values.int[start..start + size])
126 }
127 Toggle => {
128 let start = self.info.int_values_index() as usize;
129 ParmValue::Toggle(self.values.int[start] == 1)
130 }
131 Float | Color => {
132 let start = self.info.float_values_index() as usize;
133 ParmValue::Float(&self.values.float[start..start + size])
134 }
135 String | PathFileGeo | PathFile | PathFileImage | PathFileDir | Node => {
136 let start = self.info.string_values_index() as usize;
137 ParmValue::String(&self.values.string[start..start + size])
138 }
139 _ => ParmValue::NoDefault,
140 }
141 }
142
143 #[must_use]
146 pub fn menu_items(&self) -> Option<&[ParmChoiceInfo]> {
147 if let ChoiceListType::None = self.choice_list_type() {
148 return None;
149 }
150 let count = self.info.choice_count() as usize;
151 let start = self.info.choice_index() as usize;
152 Some(&self.values.menus[start..start + count])
153 }
154
155 pub fn get_tag(&self, tag_index: i32) -> Result<(String, String)> {
157 let tag_name = crate::ffi::get_asset_definition_parm_tag_name(
158 &self.info.1,
159 self.library_id,
160 self.asset_name,
161 self.info.id(),
162 tag_index,
163 )?;
164 let tag_c_str = CString::new(tag_name.clone())?;
166 let tag_value = crate::ffi::get_asset_definition_parm_tag_value(
167 &self.info.1,
168 self.library_id,
169 self.asset_name,
170 self.info.id(),
171 &tag_c_str,
172 )?;
173 Ok((tag_name, tag_value))
174 }
175}
176
177#[derive(Debug, Clone)]
179pub struct AssetLibrary {
180 pub(crate) lib_id: ffi::HAPI_AssetLibraryId,
181 pub(crate) session: Session,
182 pub file: Option<PathBuf>,
183}
184
185impl AssetLibrary {
186 pub fn from_file(session: Session, file: impl AsRef<std::path::Path>) -> Result<AssetLibrary> {
188 let file = file.as_ref().to_path_buf();
189 debug!("Loading library file: {}", file.display());
190 debug_assert!(session.is_valid());
191 let cs = CString::new(file.as_os_str().to_string_lossy().to_string())?;
192 let lib_id = crate::ffi::load_library_from_file(&cs, &session, true)?;
193 Ok(AssetLibrary {
194 lib_id,
195 session,
196 file: Some(file),
197 })
198 }
199
200 pub fn from_memory(session: Session, data: &[u8]) -> Result<AssetLibrary> {
202 debug!("Loading library from memory");
203 debug_assert!(session.is_valid());
204 let data: &[i8] = unsafe { &*(std::ptr::from_ref::<[u8]>(data) as *const [i8]) };
205 let lib_id = crate::ffi::load_library_from_memory(&session, data, true)?;
206 Ok(AssetLibrary {
207 lib_id,
208 session,
209 file: None,
210 })
211 }
212
213 pub fn get_asset_count(&self) -> Result<i32> {
215 debug_assert!(self.session.is_valid());
216 crate::ffi::get_asset_count(self.lib_id, &self.session)
217 }
218
219 pub fn get_asset_names(&self) -> Result<Vec<String>> {
221 debug_assert!(self.session.is_valid());
222 debug!(
223 "Retrieving asset names from: {:?}",
224 self.file
225 .as_deref()
226 .map_or("<memory bytes>".into(), |p| p.to_string_lossy())
227 );
228 let num_assets = self.get_asset_count()?;
229 crate::ffi::get_asset_names(self.lib_id, num_assets, &self.session)
230 .map(|a| a.into_iter().collect())
231 }
232
233 pub fn get_first_name(&self) -> Result<Option<String>> {
235 debug_assert!(self.session.is_valid());
236 self.get_asset_names().map(|names| names.first().cloned())
237 }
238
239 pub fn create_asset_for_node<T: AsRef<str>>(
243 &self,
244 name: T,
245 label: Option<&T>,
246 ) -> Result<HoudiniNode> {
247 debug!("Trying to create a node for operator: {}", name.as_ref());
250 let Some((context, operator)) = name.as_ref().split_once('/') else {
251 return Err(HapiError::Internal(format!(
252 "Incomplete node name: {}. Name must be fully qualified",
253 name.as_ref()
254 )));
255 };
256 if context.contains("::") {
258 return self.session.create_node(name.as_ref());
259 }
260 let (manager, subnet) = if context == "Sop" {
262 (None, None)
263 } else {
264 let manager_type = context.parse::<ManagerType>()?;
265 let subnet = match manager_type {
266 ManagerType::Cop => Some("img"),
267 ManagerType::Chop => Some("ch"),
268 ManagerType::Top => Some("topnet"),
269 _ => None,
270 };
271 (Some(manager_type), subnet)
272 };
273
274 let parent = match subnet {
276 Some(subnet) => {
277 let manager = manager.ok_or_else(|| {
278 HapiError::Internal(format!(
279 "Missing manager node type for context \"{context}\""
280 ))
281 })?;
282 let parent = self.session.get_manager_node(manager)?;
283 Some(
284 self.session
285 .create_node_with(subnet, parent.handle, None, false)?
286 .handle,
287 )
288 }
289 None => None,
290 };
291 let full_name = if parent.is_some() {
293 operator
294 } else {
295 name.as_ref()
296 };
297 self.session.create_node_with(
298 full_name,
299 parent,
300 label.as_ref().map(std::convert::AsRef::as_ref),
301 false,
302 )
303 }
304
305 pub fn try_create_first(&self) -> Result<HoudiniNode> {
317 debug_assert!(self.session.is_valid());
318 let name = self
319 .get_first_name()?
320 .ok_or_else(|| HapiError::Internal("Library file is empty".to_string()))?;
321 self.create_asset_for_node(name, None)
322 }
323
324 pub fn get_asset_parms(&self, asset: impl AsRef<str>) -> Result<AssetParameters> {
326 debug_assert!(self.session.is_valid());
327 let _lock = self.session.lock();
328 debug!("Reading asset parameter list of {}", asset.as_ref());
329 let asset_name = CString::new(asset.as_ref())?;
330 let parm_count =
331 crate::ffi::get_asset_def_parm_count(self.lib_id, &asset_name, &self.session)?;
332 let infos = crate::ffi::get_asset_def_parm_info(
333 self.lib_id,
334 &asset_name,
335 parm_count.total,
336 &self.session,
337 )?
338 .into_iter()
339 .map(|info| ParmInfo::new(info, self.session.clone(), None));
340 let values = crate::ffi::get_asset_def_parm_values(
341 self.lib_id,
342 &asset_name,
343 &self.session,
344 &parm_count,
345 )?;
346 let menus = values
347 .3
348 .into_iter()
349 .map(|info| ParmChoiceInfo(info, self.session.clone()));
350 let values = AssetParmValues {
351 int: values.0,
352 float: values.1,
353 string: values.2.into_iter().collect(),
354 menus: menus.collect(),
355 };
356 Ok(AssetParameters {
357 asset_name,
358 library: self.clone(),
359 infos: infos.collect(),
360 values,
361 })
362 }
363}