Skip to main content

hapi_rs/
lib.rs

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