1use crate::ffi::raw as ffi;
4use crate::ffi::raw::{ChoiceListType, ParmType};
5use crate::node::ManagerType;
6use crate::{
7 errors::Result, ffi::ParmChoiceInfo, ffi::ParmInfo, node::HoudiniNode, session::Session,
8 HapiError,
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}
43
44impl AssetParameters {
45 pub fn find_parameter(&self, name: &str) -> Option<AssetParm<'_>> {
47 self.infos
48 .iter()
49 .find(|p| p.name().unwrap() == name)
50 .map(|info| AssetParm {
51 library_id: self.library.lib_id,
52 asset_name: &self.asset_name,
53 info,
54 values: &self.values,
55 })
56 }
57}
58
59pub struct AssetParmIter<'a> {
61 library_id: i32,
62 asset_name: &'a CStr,
63 iter: std::slice::Iter<'a, ParmInfo>,
64 values: &'a AssetParmValues,
65}
66
67impl<'a> Iterator for AssetParmIter<'a> {
68 type Item = AssetParm<'a>;
69
70 fn next(&mut self) -> Option<Self::Item> {
71 self.iter.next().map(|info| AssetParm {
72 library_id: self.library_id,
73 asset_name: self.asset_name,
74 info,
75 values: self.values,
76 })
77 }
78}
79
80pub struct AssetParm<'a> {
82 library_id: i32,
83 asset_name: &'a CStr,
84 info: &'a ParmInfo,
85 values: &'a AssetParmValues,
86}
87
88impl std::ops::Deref for AssetParm<'_> {
89 type Target = ParmInfo;
90
91 fn deref(&self) -> &Self::Target {
92 self.info
93 }
94}
95
96#[derive(Debug)]
98pub enum ParmValue<'a> {
99 Int(&'a [i32]),
100 Float(&'a [f32]),
101 String(&'a [String]),
102 Toggle(bool),
103 NoDefault,
104}
105
106impl<'a> AssetParm<'a> {
107 pub fn default_value(&self) -> ParmValue<'a> {
109 let size = self.info.size() as usize;
110 use ParmType::*;
111 match self.info.parm_type() {
112 Int | Button => {
113 let start = self.info.int_values_index() as usize;
114 ParmValue::Int(&self.values.int[start..start + size])
115 }
116 Toggle => {
117 let start = self.info.int_values_index() as usize;
118 ParmValue::Toggle(self.values.int[start] == 1)
119 }
120 Float | Color => {
121 let start = self.info.float_values_index() as usize;
122 ParmValue::Float(&self.values.float[start..start + size])
123 }
124 String | PathFileGeo | PathFile | PathFileImage | PathFileDir | Node => {
125 let start = self.info.string_values_index() as usize;
126 ParmValue::String(&self.values.string[start..start + size])
127 }
128 _ => ParmValue::NoDefault,
129 }
130 }
131
132 pub fn menu_items(&self) -> Option<&[ParmChoiceInfo]> {
135 if let ChoiceListType::None = self.choice_list_type() {
136 return None;
137 }
138 let count = self.info.choice_count() as usize;
139 let start = self.info.choice_index() as usize;
140 Some(&self.values.menus[start..start + count])
141 }
142
143 pub fn get_tag(&self, tag_index: i32) -> Result<(String, String)> {
145 let tag_name = crate::ffi::get_asset_definition_parm_tag_name(
146 &self.info.1,
147 self.library_id,
148 self.asset_name,
149 self.info.id(),
150 tag_index,
151 )?;
152 let tag_c_str = unsafe { CStr::from_bytes_with_nul_unchecked(&tag_name) };
154 let tag_value = crate::ffi::get_asset_definition_parm_tag_value(
155 &self.info.1,
156 self.library_id,
157 self.asset_name,
158 self.info.id(),
159 tag_c_str,
160 )?;
161 Ok((String::from_utf8_lossy(&tag_name).to_string(), tag_value))
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct AssetLibrary {
168 pub(crate) lib_id: ffi::HAPI_AssetLibraryId,
169 pub(crate) session: Session,
170 pub file: Option<PathBuf>,
171}
172
173impl AssetLibrary {
174 pub fn from_file(session: Session, file: impl AsRef<std::path::Path>) -> Result<AssetLibrary> {
176 let file = file.as_ref().to_path_buf();
177 debug!("Loading library file: {:?}", file);
178 debug_assert!(session.is_valid());
179 let cs = CString::new(file.as_os_str().to_string_lossy().to_string())?;
180 let lib_id = crate::ffi::load_library_from_file(&cs, &session, true)?;
181 Ok(AssetLibrary {
182 lib_id,
183 session,
184 file: Some(file),
185 })
186 }
187
188 pub fn from_memory(session: Session, data: &[u8]) -> Result<AssetLibrary> {
190 debug!("Loading library from memory");
191 debug_assert!(session.is_valid());
192 let data: &[i8] = unsafe { std::mem::transmute(data) };
193 let lib_id = crate::ffi::load_library_from_memory(&session, data, true)?;
194 Ok(AssetLibrary {
195 lib_id,
196 session,
197 file: None,
198 })
199 }
200
201 pub fn get_asset_count(&self) -> Result<i32> {
203 debug_assert!(self.session.is_valid());
204 crate::ffi::get_asset_count(self.lib_id, &self.session)
205 }
206
207 pub fn get_asset_names(&self) -> Result<Vec<String>> {
209 debug_assert!(self.session.is_valid());
210 debug!("Retrieving asset names from: {:?}", self.file);
211 let num_assets = self.get_asset_count()?;
212 crate::ffi::get_asset_names(self.lib_id, num_assets, &self.session)
213 .map(|a| a.into_iter().collect())
214 }
215
216 pub fn get_first_name(&self) -> Result<Option<String>> {
218 debug_assert!(self.session.is_valid());
219 self.get_asset_names().map(|names| names.first().cloned())
220 }
221
222 pub fn create_asset_for_node<T: AsRef<str>>(
226 &self,
227 name: T,
228 label: Option<T>,
229 ) -> Result<HoudiniNode> {
230 debug!("Trying to create a node for operator: {}", name.as_ref());
233 let Some((context, operator)) = name.as_ref().split_once('/') else {
234 return Err(HapiError::internal("Node name must be fully qualified"));
235 };
236 let context = if let Some((_, context)) = context.split_once("::") {
238 context
239 } else {
240 context
241 };
242 let (manager, subnet) = if context == "Sop" {
244 (None, None)
245 } else {
246 let manager_type = context.parse::<ManagerType>()?;
247 let subnet = match manager_type {
248 ManagerType::Cop => Some("img"),
249 ManagerType::Chop => Some("ch"),
250 ManagerType::Top => Some("topnet"),
251 _ => None,
252 };
253 (Some(manager_type), subnet)
254 };
255
256 let parent = match subnet {
258 Some(subnet) => {
259 let parent = self.session.get_manager_node(manager.unwrap())?;
261 Some(
262 self.session
263 .create_node_with(subnet, parent.handle, None, false)?
264 .handle,
265 )
266 }
267 None => None,
268 };
269 let full_name = if parent.is_some() {
271 operator
272 } else {
273 name.as_ref()
274 };
275 self.session
276 .create_node_with(full_name, parent, label.as_ref().map(|v| v.as_ref()), false)
277 }
278
279 pub fn try_create_first(&self) -> Result<HoudiniNode> {
290 debug_assert!(self.session.is_valid());
291 let name = self
292 .get_first_name()?
293 .ok_or_else(|| HapiError::internal("Library file is empty"))?;
294 self.create_asset_for_node(name, None)
295 }
296
297 pub fn get_asset_parms(&self, asset: impl AsRef<str>) -> Result<AssetParameters> {
299 debug_assert!(self.session.is_valid());
300 let _lock = self.session.lock();
301 debug!("Reading asset parameter list of {}", asset.as_ref());
302 let asset_name = CString::new(asset.as_ref())?;
303 let count = crate::ffi::get_asset_def_parm_count(self.lib_id, &asset_name, &self.session)?;
304 let infos = crate::ffi::get_asset_def_parm_info(
305 self.lib_id,
306 &asset_name,
307 count.parm_count,
308 &self.session,
309 )?
310 .into_iter()
311 .map(|info| ParmInfo::new(info, self.session.clone(), None));
312 let values =
313 crate::ffi::get_asset_def_parm_values(self.lib_id, &asset_name, &self.session, &count)?;
314 let menus = values
315 .3
316 .into_iter()
317 .map(|info| ParmChoiceInfo(info, self.session.clone()));
318 let values = AssetParmValues {
319 int: values.0,
320 float: values.1,
321 string: values.2,
322 menus: menus.collect(),
323 };
324 Ok(AssetParameters {
325 asset_name,
326 library: self.clone(),
327 infos: infos.collect(),
328 values,
329 })
330 }
331}