hapi_rs/lib.rs
1#![doc(html_logo_url = "https://media.sidefx.com/uploads/products/engine/engine_orange.svg")]
2//! # Rust bindings to Houdini Engine C API.
3//!
4//! Official HAPI [documentation](https://www.sidefx.com/docs/hengine/) is bundled under
5//! `engine_docs/www.sidefx.com/docs/hengine/` for offline reference.
6//!
7//! Check out the [examples](https://github.com/alexxbb/hapi-rs/tree/main/lib/examples):
8//!
9//! `cargo run --example ...`
10//!
11//! ## Building and running
12//!
13//! **HFS** environment variable must be set for the build script to link to Houdini libraries.
14//!
15//! For runtime discovery of Houdini libraries there are several options:
16//!
17//! ### Mac & Linux
18//!
19//! **Option 1**
20//!
21//! Build with RPATH via the `RUSTFLAGS` variable:
22//! ```bash
23//! RUSTFLAGS="-C link-args=-Wl,-rpath,/path/to/hfs/dsolib" cargo build
24//! ```
25//! **Option 2**
26//!
27//! Add a cargo config file to your project: `.cargo/config`
28//!```text
29//! [target.'cfg(target_os = "linux")']
30//! rustflags = ["-C", "link-arg=-Wl,-rpath=/opt/hfs/21.0.440/dsolib"]
31//! [target.x86_64-apple-darwin]
32//! rustflags = ["-C",
33//! "link-arg=-Wl,-rpath,/Applications/Houdini/Current/Frameworks/Houdini.framework/Versions/Current/Libraries",
34//! ]
35//!```
36//! **Option 3**
37//!
38//! At runtime via env variables: `$LD_LIBRARY_PATH` on Linux and `$DYLD_LIBRARY_PATH` on macOS.
39//!
40//! ### Windows
41//! `$HFS` must be set for building.
42//! Runtime Houdini libraries have to be in `$PATH`, e.g. add `$HFS/bin`.
43//!
44//! ## Architectural overview
45//! `hapi-rs` mirrors the Houdini Engine C API (HAPI) while providing more idiomatic Rust ergonomics:
46//! - Every `HAPI_*` struct becomes a dedicated Rust wrapper with `get_*`, `set_*`, and `with_*` helpers
47//! (for example `HAPI_NodeInfo` -> [`node::NodeInfo`], `HAPI_GeoInfo` -> [`geometry::GeoInfo`]).
48//! - Enums drop the `HAPI_` prefix and use concise variant names while preserving the C ordering so that
49//! official documentation can be cross-referenced easily.
50//! - Most structs implement [`Default`] and expose builder-like methods to keep initialization readable.
51//! - String conversions are centralized in [`stringhandle`] to hide `HAPI_StringHandle` management and to
52//! provide iterators that either keep data as `CString` or eagerly convert to `String` when needed.
53//! - [`asset`], [`session`], [`node`], [`geometry`], [`attribute`], [`parameter`], [`material`], [`volume`],
54//! and [`pdg`] modules roughly follow the sections from the Houdini Engine manual so that the mental model
55//! stays close to the C API.
56//! - Raw HAPI symbols remain accessible through [`raw`], the bindgen-generated module re-exported for
57//! advanced integrations when you need to drop straight to the C API.
58//!
59//! ## Sessions and servers
60//! The [`session::Session`] type encapsulates a live connection to Houdini Engine. Internally it stores an
61//! `Arc<SessionInner>` so cloning a session is cheap and the connection stays alive until the last clone is
62//! dropped. Houdini guarantees that a single session handle can be used concurrently; consequently the Rust
63//! wrapper is [`Send`] + [`Sync`] and only falls back to a private [`parking_lot::ReentrantMutex`] when HAPI
64//! needs serialized calls. When [`session::SessionOptions::cleanup`] is enabled (the default), dropping the
65//! last clone will automatically clean up the session and shut down the associated server.
66//!
67//! [`session::simple_session`] bootstraps a Thrift shared-memory server via [`session::new_thrift_session`]
68//! and [`server::ServerOptions::shared_memory_with_defaults`]. You can override transports, buffer sizes,
69//! environment variables, or timeouts with [`server::ServerOptions`]—see `lib/examples/setup_server.rs` for
70//! an advanced configuration.
71//!
72//! License preference can be set with [`server::ServerOptions::with_license_preference`].
73//!
74//! Sessions expose helpers that map closely to the HAPI entry points:
75//! - [`session::Session::load_asset_file`] returns an [`asset::AssetLibrary`] so you can instantiate HDAs.
76//! - [`session::Session::create_node`], [`session::Session::node_builder`], and
77//! [`session::Session::create_input_node`] for editable geometry inputs.
78//! - [`session::Session::set_server_var`] / [`session::Session::get_server_var`] variable APIs.
79//! - [`session::Session::cook`] reports [`session::CookResult`] when you run in threaded mode.
80//!
81//! ```rust
82//! use hapi_rs::session::simple_session;
83//! use std::path::PathBuf;
84//!
85//! fn main() -> hapi_rs::Result<()> {
86//! let session = simple_session().unwrap();
87//! assert!(session.is_valid());
88//! let hda = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../otls/hapi_geo.hda");
89//! let library = session.load_asset_file(&hda)?;
90//! let node = library.try_create_first()?;
91//! node.cook_blocking()?;
92//! Ok(())
93//! }
94//! ```
95//!
96//! ## HoudiniNode and node APIs
97//! HAPI node ids become [`node::NodeHandle`], a lightweight wrapper that you often receive when traversing
98//! networks. Call [`node::NodeHandle::to_node`] to promote the handle into a [`node::HoudiniNode`], which
99//! stores the handle, [`node::NodeInfo`], and the owning [`session::Session`]. `HoudiniNode` is also
100//! [`Clone`] + [`Send`] + [`Sync`] so you can keep references across threads or store them inside higher
101//! level abstractions such as [`geometry::Geometry`] or [`pdg::TopNode`].
102//!
103//! Nodes expose the most commonly used HAPI functionality:
104//! - [`node::HoudiniNode::cook_blocking`] / [`node::HoudiniNode::cook_with_options`] forward to
105//! `HAPI_CookNode` and integrate with [`session::Session::cook`] for threaded servers.
106//! - [`node::HoudiniNode::geometry`] returns a [`geometry::Geometry`] for SOP nodes or follows the display flag
107//! when called on OBJ nodes.
108//! - [`node::HoudiniNode::parameters`] retrieves strongly-typed [`parameter::Parameter`] values.
109//! - Networking helpers such as [`node::HoudiniNode::find_children_by_type`] or [`node::ManagerNode`] mirror
110//! the C API utilities.
111//! - File IO helpers ([`node::HoudiniNode::save_to_file`], [`node::HoudiniNode::load_from_file`]) keep the
112//! naming/parsing identical to HAPI.
113//!
114//! For complete node-network demos see `lib/examples/node_networks.rs` (wiring SOPs), `lib/examples/node_errors.rs`
115//! (reading cook errors), and `lib/examples/live_session.rs` (re-using an existing Houdini session).
116//!
117//! ## Geometry workflow
118//! [`geometry::Geometry`] represents SOP outputs and wraps [`geometry::GeoInfo`] so you get immediate access to
119//! element counts, part metadata, group names, or material assignments. When you create a node from
120//! `../otls/hapi_geo.hda`, [`node::HoudiniNode::geometry`] either returns the SOP node directly or finds the
121//! display child for OBJ nodes. [`session::Session::create_input_node`] and
122//! [`session::Session::create_input_curve_node`] expose editable SOP nodes if you want to push points back
123//! into Houdini.
124//!
125//! Each geometry is composed of [`geometry::PartInfo`] entries (points, meshes, curves, packed primitives, …).
126//! Use [`geometry::Geometry::partitions`] or [`geometry::Geometry::part_info`] to explore them, then rely on
127//! helpers such as [`geometry::Geometry::get_materials`] to inspect [`geometry::Materials`]. Materials can be
128//! promoted to [`material::Material`] for texture extraction—`lib/examples/materials.rs` shows how to render
129//! maps from `../otls/sesi/SideFX_spaceship.hda`.
130//!
131//! The curve and group APIs map directly to HAPI (`get_curve_counts`, `get_group_names`, etc.); see
132//! `lib/examples/object_geos_parts.rs` and `lib/examples/curve_output.rs` for in-depth geometry traversals.
133//!
134//! ```rust
135//! use hapi_rs::{
136//! geometry::PartType,
137//! session::simple_session,
138//! };
139//! use std::path::PathBuf;
140//!
141//! fn main() -> hapi_rs::Result<()> {
142//! let session = simple_session().unwrap();
143//! let hda = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../otls/hapi_geo.hda");
144//! let library = session.load_asset_file(&hda)?;
145//! let node = library.try_create_first()?;
146//! node.cook_blocking()?;
147//! let geometry = node.geometry()?.expect("SOP geometry");
148//! let mesh = geometry
149//! .partitions()?
150//! .into_iter()
151//! .find(|part| part.part_type() == PartType::Mesh)
152//! .expect("mesh part");
153//! assert!(mesh.point_count() > 0);
154//! Ok(())
155//! }
156//! ```
157//! Some underlying C structs don't provide a direct way of creating them or they might not provide methods
158//! for modifying them, due to this crate's attempt for proper data encapsulation, minimizing noise and improving safety.
159//! Structs that you **do** need the ability to create, implement [Default] and some implement a `Builder Pattern` with convenient `with_` and `set_` methods:
160//! ```rust
161//! use hapi_rs::geometry::{PartInfo, PartType};
162//! let part_info = PartInfo::default()
163//! .with_part_type(PartType::Mesh)
164//! .with_face_count(6);
165//! ```
166//!
167//! ## Attribute access and type-safe downcasting
168//! Attributes come back as a dynamic [`attribute::Attribute`] wrapper. You can inspect its storage using
169//! [`attribute::Attribute::storage`] and downcast it into concrete types such as [`attribute::NumericAttr`],
170//! [`attribute::StringAttr`], or [`attribute::DictionaryAttr`]. This mirrors the HAPI concept of inspecting
171//! [`geometry::AttributeInfo`] first and then choosing the right getter; the downcast enforces the check at
172//! compile time.
173//!
174//! [`geometry::Geometry::get_attribute`] will fetch any attribute by owner/name, while convenience helpers such
175//! as [`geometry::Geometry::get_position_attribute`] cover common cases. To create new attributes you build an
176//! [`geometry::AttributeInfo`] (it implements [`Default`] + builder setters), then call one of the
177//! `add_*_attribute` methods. Array and dictionary attributes lean on [`attribute::DataArray`] and the
178//! async helpers in [`attribute::async_]` for large transfers. Examples like `lib/examples/curve_output.rs` and
179//! `lib/examples/groups.rs` showcase real-world usages.
180//!
181//! ```rust
182//! use hapi_rs::{
183//! attribute::NumericAttr,
184//! geometry::{AttributeInfo, AttributeOwner, PartType, StorageType},
185//! session::simple_session,
186//! };
187//! use std::path::PathBuf;
188//!
189//! fn main() -> hapi_rs::Result<()> {
190//! let session = simple_session().unwrap();
191//! let hda = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../otls/hapi_geo.hda");
192//! let library = session.load_asset_file(&hda)?;
193//! let node = library.try_create_first()?;
194//! node.cook_blocking()?;
195//! let geometry = node.geometry()?.expect("SOP geometry");
196//! let part = geometry
197//! .partitions()?
198//! .into_iter()
199//! .find(|info| info.part_type() == PartType::Mesh)
200//! .expect("mesh part");
201//! let attr = geometry
202//! .get_attribute(part.part_id(), AttributeOwner::Point, "P")?
203//! .expect("P attribute");
204//! let positions = attr
205//! .downcast::<NumericAttr<f32>>()
206//! .expect("numeric P data");
207//! let values = positions.get(part.part_id())?;
208//! assert!(!values.is_empty());
209//!
210//! let mut info = AttributeInfo::default();
211//! info.set_owner(AttributeOwner::Point);
212//! info.set_storage(StorageType::Float);
213//! info.set_tuple_size(1);
214//! info.set_count(part.point_count());
215//! let weights = geometry.add_numeric_attribute::<f32>("rs_weight", part.part_id(), info)?;
216//! let fill = vec![1.0f32; part.point_count() as usize];
217//! weights.set(part.part_id(), &fill)?;
218//! Ok(())
219//! }
220//! ```
221//!
222//! ## Parameters and UI metadata
223//! [`parameter::Parameter`] is an enum that covers all `HAPI_ParmType` values, while
224//! [`parameter::ParmBaseTrait`] exposes shared helpers (`name`, `label`, `size`, etc.). Matching on `Parameter`
225//! gives you access to strongly typed wrappers such as [`parameter::FloatParameter`],
226//! [`parameter::IntParameter`], [`parameter::StringParameter`], or button/toggle helpers. You can inspect menu
227//! metadata, manipulate multiparms, attach expressions/animation curves, or call [`parameter::IntParameter::press_button`]
228//! for script callbacks. [`parameter::ParmInfo`] mirrors `HAPI_ParmInfo` for low-level needs.
229//!
230//! Always cook an HDA before touching its parameters so that defaults are initialized. The snippet below uses
231//! `../otls/hapi_parms.hda`, the same file leveraged by `lib/examples/parameters.rs`, and demonstrates how to
232//! branch on parameter types. For more elaborate formatting of menu values see that example; it prints a table
233//! of every parameter on the asset.
234//!
235//! ```rust
236//! use hapi_rs::{
237//! parameter::{ParmBaseTrait, Parameter},
238//! session::simple_session,
239//! };
240//! use std::path::PathBuf;
241//!
242//! fn main() -> hapi_rs::Result<()> {
243//! let session = simple_session().unwrap();
244//! let hda = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../otls/hapi_parms.hda");
245//! let library = session.load_asset_file(&hda)?;
246//! let node = library.try_create_first()?;
247//! node.cook_blocking()?;
248//! if let Parameter::Float(color) = node.parameter("color")? {
249//! color.set_array([0.25, 0.5, 0.75])?;
250//! assert_eq!(color.size(), 3);
251//! }
252//! if let Parameter::Int(toggle) = node.parameter("toggle")? {
253//! let next = if toggle.get(0)? == 0 { 1 } else { 0 };
254//! toggle.set(0, next)?;
255//! }
256//! if let Parameter::String(path) = node.parameter("op_path")? {
257//! if let Some(target) = path.get_value_as_node()? {
258//! assert!(target.is_valid(&node.session)?);
259//! }
260//! }
261//! Ok(())
262//! }
263//! ```
264//!
265//! ## Error handling and diagnostics
266//! Every public API returns [`Result`], an alias for `std::result::Result<T, [`HapiError`]>`.
267//! `HapiError::Hapi` stores the [`errors::HapiResultCode`] plus an optional server message fetched through
268//! [`session::Session::get_status_string`]. Additional context strings accumulate automatically when you call
269//! `.context(...)` / `.with_context(...)` using the helper methods defined in the `errors` module, making it
270//! easier to track which operation failed (for example `geometry::Geometry::get_attribute` annotates the
271//! attribute name and owner).
272//!
273//! Other variants (null-byte, UTF-8, IO, internal) map directly to common Rust errors. When a cook happens in
274//! threaded mode, methods return [`session::CookResult`] so you can inspect the cook-state message even if the
275//! original call succeeded. `lib/examples/node_errors.rs` demonstrates how to read verbose cook logs and status
276//! codes, while `lib/examples/materials.rs` shows how to propagate file IO errors during texture extraction.
277//!
278//! String-heavy APIs such as parameter values or attribute names rely on [`stringhandle::StringArray`] to batch
279//! conversions and mirror `HAPI_StringHandle` semantics. Use [`session::Session::get_string`] or
280//! [`session::Session::get_string_batch`] if you need direct access, or prefer the higher-level helpers where
281//! possible.
282
283pub mod asset;
284pub mod attribute;
285pub mod geometry;
286pub mod material;
287pub mod node;
288pub mod cop;
289pub mod parameter;
290pub mod server;
291pub mod session;
292pub mod stringhandle;
293pub mod volume;
294pub mod pdg;
295mod errors;
296mod utils;
297mod ffi;
298
299pub use errors::{HapiError, HapiResult, HapiResultCode, Result};
300pub use ffi::enums;
301pub use ffi::raw;
302
303/// Houdini version this library was build upon
304#[derive(Debug)]
305pub struct HoudiniVersion {
306 pub major: u32,
307 pub minor: u32,
308 pub build: u32,
309 pub patch: u32,
310}
311
312/// Engine version this library was build upon
313#[derive(Debug)]
314pub struct EngineVersion {
315 pub major: u32,
316 pub minor: u32,
317 pub api: u32,
318}
319
320/// Houdini version this library was build upon
321pub const HOUDINI_VERSION: HoudiniVersion = HoudiniVersion {
322 major: raw::HAPI_VERSION_HOUDINI_MAJOR,
323 minor: raw::HAPI_VERSION_HOUDINI_MINOR,
324 build: raw::HAPI_VERSION_HOUDINI_BUILD,
325 patch: raw::HAPI_VERSION_HOUDINI_PATCH,
326};
327
328/// Engine version this library was build upon
329pub const ENGINE_VERSION: EngineVersion = EngineVersion {
330 major: raw::HAPI_VERSION_HOUDINI_ENGINE_MAJOR,
331 minor: raw::HAPI_VERSION_HOUDINI_ENGINE_MINOR,
332 api: raw::HAPI_VERSION_HOUDINI_ENGINE_API,
333};