diff --git a/crates/opencascade-sys/include/wrapper.hxx b/crates/opencascade-sys/include/wrapper.hxx index 01f462b6..6c071570 100644 --- a/crates/opencascade-sys/include/wrapper.hxx +++ b/crates/opencascade-sys/include/wrapper.hxx @@ -79,6 +79,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -88,6 +95,105 @@ #include #include +// BEGIN Tdf stuff +typedef opencascade::handle HandleTdfData; +typedef opencascade::handle HandleTdfDelta; + +inline std::unique_ptr TDF_Data_new() { + return std::unique_ptr(new opencascade::handle(new TDF_Data())); +} + +inline std::unique_ptr TDF_Data_root(const HandleTdfData& data) { + return std::unique_ptr(new TDF_Label(data->Root())); +} + +inline std::unique_ptr TDF_Label_new_child(const TDF_Label& label) { + return std::unique_ptr(new TDF_Label(label.NewChild())); +} + +inline bool TDF_Label_is_null(const TDF_Label& label) { + return label.IsNull(); +} +inline std::unique_ptr TDF_Transaction_new(const HandleTdfData& data) { + return std::unique_ptr(new TDF_Transaction(data)); +} + +inline Standard_Integer TDF_Transaction_open(TDF_Transaction& transaction) { + return transaction.Open(); +} + +inline std::unique_ptr TDF_Transaction_commit(TDF_Transaction& transaction) { + return std::unique_ptr(new opencascade::handle(transaction.Commit(true))); +} + +inline void TDF_Transaction_abort(TDF_Transaction& transaction) { + transaction.Abort(); +} + +inline bool TDF_Transaction_is_open(const TDF_Transaction& transaction) { + return transaction.IsOpen(); +} +inline std::unique_ptr TDF_Data_undo( + HandleTdfData& data, + const HandleTdfDelta& delta +) { + return std::unique_ptr( + new HandleTdfDelta(data->Undo(delta, Standard_True)) + ); +} + +inline std::unique_ptr TNaming_NamedShape_Get( + const Handle_TNaming_NamedShape& ns +) { + return std::unique_ptr(new TopoDS_Shape(ns->Get())); +} + +// BEGIN TNaming section of Tdf stuff + + + +inline std::unique_ptr TNaming_Builder_ctor(const TDF_Label& label) { + return std::unique_ptr(new TNaming_Builder(label)); +} + +inline void TNaming_Builder_generated(TNaming_Builder& builder, const TopoDS_Shape& newShape) { + builder.Generated(newShape); +} + +inline void TNaming_Builder_generated_with_old(TNaming_Builder& builder, const TopoDS_Shape& oldShape, const TopoDS_Shape& newShape) { + builder.Generated(oldShape, newShape); +} + +inline void TNaming_Builder_modify(TNaming_Builder& builder, const TopoDS_Shape& oldShape, const TopoDS_Shape& newShape) { + builder.Modify(oldShape, newShape); +} + +inline void TNaming_Builder_delete(TNaming_Builder& builder, const TopoDS_Shape& oldShape) { + builder.Delete(oldShape); +} + +inline void TNaming_Builder_select(TNaming_Builder& builder, const TopoDS_Shape& shape, const TopoDS_Shape& inShape) { + builder.Select(shape, inShape); +} + +inline std::unique_ptr TNaming_Builder_named_shape( + const TNaming_Builder& builder +) { + return std::unique_ptr( + new Handle_TNaming_NamedShape(builder.NamedShape()) + ); +} + +inline std::unique_ptr TNaming_Tool_original_shape( + const Handle_TNaming_NamedShape& ns +) { + return std::unique_ptr( + new TopoDS_Shape(TNaming_Tool::OriginalShape(ns)) + ); +} +// END TNaming section of Tdf stuff +// END Tdf stuff + // Generic template constructor template std::unique_ptr construct_unique(Args... args) { return std::unique_ptr(new T(args...)); diff --git a/crates/opencascade-sys/src/lib.rs b/crates/opencascade-sys/src/lib.rs index 77ca10d7..f95af9d8 100644 --- a/crates/opencascade-sys/src/lib.rs +++ b/crates/opencascade-sys/src/lib.rs @@ -74,6 +74,63 @@ pub mod ffi { #[cxx_name = "construct_unique"] pub fn Message_ProgressRange_ctor() -> UniquePtr; + // TDF_Data management + type HandleTdfData; + type TDF_Label; + type TDF_Transaction; + type HandleTdfDelta; + + pub fn TDF_Data_new() -> UniquePtr; + pub fn TDF_Data_root(data: &HandleTdfData) -> UniquePtr; + pub fn TDF_Label_new_child(label: &TDF_Label) -> UniquePtr; + pub fn TDF_Label_is_null(label: &TDF_Label) -> bool; + pub fn TDF_Transaction_new(data: &HandleTdfData) -> UniquePtr; + pub fn TDF_Transaction_open(transaction: Pin<&mut TDF_Transaction>) -> i32; + pub fn TDF_Transaction_commit( + transaction: Pin<&mut TDF_Transaction>, + ) -> UniquePtr; + pub fn TDF_Transaction_abort(transaction: Pin<&mut TDF_Transaction>); + pub fn TDF_Transaction_is_open(transaction: &TDF_Transaction) -> bool; + pub fn TDF_Data_undo( + data: Pin<&mut HandleTdfData>, + delta: &HandleTdfDelta, + ) -> UniquePtr; + + // TNaming + type TNaming_Builder; + + pub fn TNaming_Builder_ctor(label: &TDF_Label) -> UniquePtr; + pub fn TNaming_Builder_generated( + builder: Pin<&mut TNaming_Builder>, + new_shape: &TopoDS_Shape, + ); + pub fn TNaming_Builder_generated_with_old( + builder: Pin<&mut TNaming_Builder>, + old_shape: &TopoDS_Shape, + new_shape: &TopoDS_Shape, + ); + pub fn TNaming_Builder_modify( + builder: Pin<&mut TNaming_Builder>, + old_shape: &TopoDS_Shape, + new_shape: &TopoDS_Shape, + ); + pub fn TNaming_Builder_delete(builder: Pin<&mut TNaming_Builder>, old_shape: &TopoDS_Shape); + pub fn TNaming_Builder_select( + builder: Pin<&mut TNaming_Builder>, + shape: &TopoDS_Shape, + in_shape: &TopoDS_Shape, + ); + type Handle_TNaming_NamedShape; + + pub fn TNaming_Builder_named_shape( + builder: &TNaming_Builder, + ) -> UniquePtr; + + pub fn TNaming_Tool_original_shape( + ns: &Handle_TNaming_NamedShape, + ) -> UniquePtr; + pub fn TNaming_NamedShape_Get(ns: &Handle_TNaming_NamedShape) -> UniquePtr; + // Handles type HandleStandardType; type HandleGeomCurve; @@ -462,7 +519,9 @@ pub mod ffi { pub fn IsNull(self: &TopoDS_Shape) -> bool; pub fn IsEqual(self: &TopoDS_Shape, other: &TopoDS_Shape) -> bool; + pub fn IsSame(self: &TopoDS_Shape, other: &TopoDS_Shape) -> bool; pub fn ShapeType(self: &TopoDS_Shape) -> TopAbs_ShapeEnum; + pub fn Location(self: &TopoDS_Shape) -> &TopLoc_Location; type TopAbs_Orientation; pub fn Orientation(self: &TopoDS_Shape) -> TopAbs_Orientation; @@ -1246,6 +1305,7 @@ pub mod ffi { pub fn IsDone(self: &BRepMesh_IncrementalMesh) -> bool; type TopLoc_Location; + pub fn IsIdentity(self: &TopLoc_Location) -> bool; #[cxx_name = "construct_unique"] pub fn TopLoc_Location_ctor() -> UniquePtr; diff --git a/crates/opencascade/src/lib.rs b/crates/opencascade/src/lib.rs index 9c7bb68a..54f57a69 100644 --- a/crates/opencascade/src/lib.rs +++ b/crates/opencascade/src/lib.rs @@ -10,6 +10,7 @@ pub mod workplane; mod law_function; mod make_pipe_shell; +pub mod tdf; #[derive(Error, Debug)] pub enum Error { diff --git a/crates/opencascade/src/primitives/shape.rs b/crates/opencascade/src/primitives/shape.rs index 439cb0a6..07437b72 100644 --- a/crates/opencascade/src/primitives/shape.rs +++ b/crates/opencascade/src/primitives/shape.rs @@ -678,6 +678,14 @@ impl Shape { Self::from_shape(upgrader.Shape()) } + /// Reads the translation component of this shape's location transform. + /// For shapes with composed locations (e.g. nested assemblies), this + /// reflects the full composed transform, not just the outermost translation. + pub fn translation(&self) -> DVec3 { + let location = self.inner.as_ref().unwrap().Location(); + let trsf = ffi::TopLoc_Location_Transformation(&location); + DVec3::new(trsf.Value(1, 4), trsf.Value(2, 4), trsf.Value(3, 4)) + } pub fn set_global_translation(&mut self, translation: DVec3) { let mut transform = ffi::new_transform(); let translation_vec = make_vec(translation); diff --git a/crates/opencascade/src/tdf.rs b/crates/opencascade/src/tdf.rs new file mode 100644 index 00000000..36796771 --- /dev/null +++ b/crates/opencascade/src/tdf.rs @@ -0,0 +1,381 @@ +//! Bindings for OCCT's TDF (Topological Data Framework) and TNaming packages. +//! +//! [`TdfData`] is the top-level datastructure containing a tree of [`TdfLabel`]s +//! +//! The use of these labels within the tree is the OCCT way of capturing moments in the topological story of a +//! "document", and the tree describes how these moments come together. +//! +//! [`TdfTransaction`]s are used to scope changes in a document, and committed changes are captured +//! by [`TdfDelta`]. Feeding a [`TdfDelta`] into a document via [`TdfData::undo`] enables a +//! reversion of a transaction, with said [`TdfData::undo`] emitting an inverted of the delta to +//! allow for a "backwards-undo", i.e. "redo" +//! +//! That leaves us with [`TnamingBuilder`]: It is the write interface for TNaming: it records the +//! provenance of topological shapes into the document, establishing the +//! relationships that allow stable shape references to survive model rebuilds. +//! +//! # Example +//! +//! ``` +//! use opencascade::tdf::{TdfData, TnamingBuilder}; +//! use opencascade::primitives::Shape; +//! use glam::dvec3; +//! +//! // A document and two shapes: one before, and after a translation +//! let original = Shape::box_centered(10.0, 10.0, 10.0); +//! let mut translated = Shape::box_centered(10.0, 10.0, 10.0); +//! translated.set_global_translation(dvec3(5.0, 0.0, 0.0)); +//! +//! let mut doc = TdfData::new(); +//! +//! // Label hierarchy mirrors the operation tree +//! let mut tx = doc.transaction(); +//! tx.open(); +//! let label = doc.root().new_child(); +//! +//! // Record the translation as a shape evolution. +//! // The builder borrows the transaction and must be dropped before commit. +//! // Future API iterations will enforce this structurally via closure arguments. +//! { +//! let mut builder = TnamingBuilder::new(&tx, &label); +//! builder.modify( +//! &original, +//! &translated, +//! ); +//! } +//! +//! // Commit captures the delta; undo reverses it +//! let delta = tx.commit(); +//! let redo_delta = doc.undo(delta); +//! +//! // Redo reapplies +//! let _ = doc.undo(redo_delta); +//! ``` +use cxx::UniquePtr; +use opencascade_sys::ffi; +use std::marker::PhantomData; + +use crate::primitives::Shape; + +/// ```compile_fail +/// use opencascade::tdf::TdfData; +/// let root = { +/// let doc = TdfData::new(); +/// doc.root() +/// }; +/// ``` +/// ```compile_fail +/// use opencascade::tdf::TdfData; +/// let tx = { +/// let doc = TdfData::new(); +/// doc.transaction() +/// }; +/// ``` +pub struct TdfData { + inner: UniquePtr, +} +/// Owned record of what changed in a committed transaction. Passed back +/// into [`TdfData::undo`] to reverse those changes. The inverse delta +/// returned by undo enables redo, and deltas can be retained externally +/// for history traversal or version management. +pub struct TdfDelta { + pub(crate) inner: UniquePtr, +} + +impl TdfData { + pub fn new() -> Self { + Self { inner: ffi::TDF_Data_new() } + } + + pub fn root(&self) -> TdfLabel<'_> { + TdfLabel { inner: ffi::TDF_Data_root(self.inner.as_ref().unwrap()), _phantom: PhantomData } + } + pub fn transaction(&self) -> TdfTransaction<'_> { + TdfTransaction { + inner: ffi::TDF_Transaction_new(self.inner.as_ref().unwrap()), + _phantom: PhantomData, + } + } + /// Consumes the passed in `delta`, reverting the [`TdfTransaction::commit`] (and friends) that + /// constructed it. + /// ``` + /// # use opencascade::tdf::{TdfData, TnamingBuilder, TnamingNamedShape}; + /// # use opencascade::primitives::Shape; + /// # use glam::{ dvec3, DVec3 }; + /// let original = Shape::box_centered(10.0, 10.0, 10.0); + /// let mut translated = Shape::box_centered(10.0, 10.0, 10.0); + /// translated.set_global_translation(dvec3(5.0, 0.0, 0.0)); + /// let mut doc = TdfData::new(); + /// let mut tx = doc.transaction(); + /// tx.open(); + /// let label = doc.root().new_child(); + /// // Record the translation as a shape evolution. + /// // The builder borrows the transaction and must be dropped before commit. + /// // Future API iterations will enforce this structurally via closure arguments. + /// let named_shape; + /// { + /// let mut builder = TnamingBuilder::new(&tx, &label); + /// builder.modify( + /// &original, + /// &translated, + /// ); + /// named_shape = builder.named_shape(); + /// } + /// + /// let delta = tx.commit(); + /// // after commit: active shape is the translated one + /// assert_ne!(named_shape.get().translation(), DVec3::ZERO); + /// let redo_delta = doc.undo(delta); + /// // after undo: active shape reverts + /// assert_eq!(named_shape.get().translation(), DVec3::ZERO); + /// let _ = doc.undo(redo_delta); + /// // after redo: translated again + /// assert_ne!(named_shape.get().translation(), DVec3::ZERO); + /// ``` + #[must_use] + pub fn undo(&mut self, delta: TdfDelta) -> TdfDelta { + // unwrap: A [`TdfDelta`] not being a valid handle implies it was not constructed through + // the use of [`TdfTransaction::commit`] at time of writing. + TdfDelta { inner: ffi::TDF_Data_undo(self.inner.pin_mut(), delta.inner.as_ref().unwrap()) } + } +} + +pub struct TdfLabel<'doc> { + inner: UniquePtr, + _phantom: PhantomData<&'doc TdfData>, +} + +impl<'doc> TdfLabel<'doc> { + pub fn new_child(&self) -> TdfLabel<'doc> { + TdfLabel { + inner: ffi::TDF_Label_new_child(self.inner.as_ref().unwrap()), + _phantom: PhantomData, + } + } + + pub fn is_null(&self) -> bool { + ffi::TDF_Label_is_null(self.inner.as_ref().unwrap()) + } +} +/// Brackets a set of mutations to the label tree. The record of what +/// changed is not retained here, as it is transfers to [`TdfDelta`] on commit. +/// +/// ```compile_fail +/// use opencascade::tdf::{TdfData, TnamingBuilder}; +/// use opencascade::primitives::Shape; +/// let original = Shape::box_centered(10.0, 10.0, 10.0); +/// let mut doc = TdfData::new(); +/// let mut tx = doc.transaction(); +/// tx.open(); +/// let label = doc.root().new_child(); +/// let mut builder = TnamingBuilder::new(&tx, &label); +/// let _ = tx.commit(); +/// builder.select(&original, &original); // use after commit attempt +/// ``` +pub struct TdfTransaction<'doc> { + inner: UniquePtr, + _phantom: PhantomData<&'doc TdfData>, +} + +impl<'doc> TdfTransaction<'doc> { + // TODO: this i32 return value needs to be new-typed and have an interface that encapsulates + // its nature. Refer to OCCT support for nested transactions and how this value indexes into + // them. For now, the nesting is always 1, but that will change... + pub fn open(&mut self) -> i32 { + ffi::TDF_Transaction_open(self.inner.pin_mut()) + } + + pub fn is_open(&self) -> bool { + ffi::TDF_Transaction_is_open(self.inner.as_ref().unwrap()) + } + + /// "Closes the bracket" on this transaction, committing the change to the [`TdfData`] + /// Dropping the returned [`TdfDelta`] permanently discards the ability + /// to undo this transaction. + #[must_use] + pub fn commit(mut self) -> TdfDelta { + TdfDelta { inner: ffi::TDF_Transaction_commit(self.inner.pin_mut()) } + } + + pub fn abort(mut self) { + ffi::TDF_Transaction_abort(self.inner.pin_mut()) + } +} +/// The write interface for topological naming. Records shape evolution, i.e. +/// the provenance relationships between pre- and post-operation shapes, +/// into a [`TdfLabel`]. These records are what allow stable references +/// to topological entities to survive model rebuilds. +/// +/// An open [`TdfTransaction`] is required for the builder's full lifetime +/// because the underlying attribute write must be captured in a delta. +/// The borrow checker enforces this: [`TdfTransaction::commit`] cannot +/// be called while any builder is live. +/// +/// # Correctness +/// [`TdfTransaction::open`] must be called before constructing a builder. +/// The borrow checker enforces that the transaction outlives the builder, +/// but cannot enforce that the transaction is open. Attempting to construct a builder +/// on an unopened transaction will panic at the OCCT level. +/// +/// Correct usage: +/// ``` +/// use opencascade::tdf::{TdfData, TnamingBuilder}; +/// let mut doc = TdfData::new(); +/// let mut tx = doc.transaction(); +/// tx.open(); // must precede builder construction +/// let label = doc.root().new_child(); +/// let mut builder = TnamingBuilder::new(&tx, &label); +/// ``` +/// +/// Future API iterations will enforce this structurally, e.g. via closure arguments, +/// making the open/build/commit sequence impossible to misorder. +pub struct TnamingBuilder<'tx, 'doc: 'tx> { + inner: UniquePtr, + _phantom: PhantomData<&'tx TdfTransaction<'doc>>, +} + +impl<'tx, 'doc: 'tx> TnamingBuilder<'tx, 'doc> { + /// The `_tx` argument is passed in to bind the lifetime of the [`Self`] to the scope of the + /// transaction it is used within + pub fn new(_tx: &'tx TdfTransaction<'doc>, label: &TdfLabel<'doc>) -> Self { + Self { + inner: ffi::TNaming_Builder_ctor(label.inner.as_ref().unwrap()), + _phantom: PhantomData, + } + } + /// Use `old_shape: None` for primitives with no predecessor. + pub fn generated(&mut self, old_shape: Option<&Shape>, new_shape: &Shape) { + match old_shape { + None => ffi::TNaming_Builder_generated( + self.inner.pin_mut(), + new_shape.inner.as_ref().unwrap(), + ), + Some(old) => ffi::TNaming_Builder_generated_with_old( + self.inner.pin_mut(), + old.inner.as_ref().unwrap(), + new_shape.inner.as_ref().unwrap(), + ), + } + } + pub fn modify(&mut self, old_shape: &Shape, new_shape: &Shape) { + ffi::TNaming_Builder_modify( + self.inner.pin_mut(), + old_shape.inner.as_ref().unwrap(), + new_shape.inner.as_ref().unwrap(), + ); + } + + pub fn delete(&mut self, old_shape: &Shape) { + ffi::TNaming_Builder_delete(self.inner.pin_mut(), old_shape.inner.as_ref().unwrap()); + } + + pub fn select(&mut self, shape: &Shape, in_shape: &Shape) { + ffi::TNaming_Builder_select( + self.inner.pin_mut(), + shape.inner.as_ref().unwrap(), + in_shape.inner.as_ref().unwrap(), + ); + } + + pub fn named_shape(&self) -> TnamingNamedShape { + TnamingNamedShape { inner: ffi::TNaming_Builder_named_shape(self.inner.as_ref().unwrap()) } + } +} + +/// A handle to the [`TNaming_NamedShape`] attribute written to a [`TdfLabel`] +/// by [`TnamingBuilder`]. Retaining this after the builder is dropped allows +/// querying the recorded shape evolution after commit and across undo/redo +/// boundaries. +pub struct TnamingNamedShape { + pub(crate) inner: UniquePtr, +} + +impl TnamingNamedShape { + pub fn original_shape(&self) -> Shape { + Shape { inner: ffi::TNaming_Tool_original_shape(self.inner.as_ref().unwrap()) } + } + /// The new shape from the recorded evolution pair. Reflects the current undo/redo state + pub fn get(&self) -> Shape { + Shape { inner: ffi::TNaming_NamedShape_Get(self.inner.as_ref().unwrap()) } + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tdf_data_root_is_not_null() { + let doc = TdfData::new(); + let root = doc.root(); + assert!(!root.is_null()); + } + + #[test] + fn test_new_child_with_transaction() { + let doc = TdfData::new(); + let mut tx = doc.transaction(); + tx.open(); + let root = doc.root(); + let child = root.new_child(); + assert!(!child.is_null()); + let _ = tx.commit(); + } + #[test] + fn test_tnaming_builder_constructs() { + let doc = TdfData::new(); + let mut tx = doc.transaction(); + tx.open(); + let root = doc.root(); + let label = root.new_child(); + let _builder = TnamingBuilder::new(&tx, &label); + } + #[test] + fn test_tnaming_builder_generated() { + use crate::primitives::Shape; + let doc = TdfData::new(); + let mut tx = doc.transaction(); + tx.open(); + let root = doc.root(); + let label = root.new_child(); + let mut builder = TnamingBuilder::new(&tx, &label); + let shape = Shape::sphere(1.0).build(); + builder.generated(None, &shape); + } + #[test] + fn test_tnaming_undo_round_trip() { + use crate::primitives::Shape; + use glam::{dvec3, DVec3}; + + let original = Shape::box_centered(10.0, 10.0, 10.0); + let mut translated = Shape::box_centered(10.0, 10.0, 10.0); + translated.set_global_translation(dvec3(5.0, 0.0, 0.0)); + + let mut doc = TdfData::new(); + let mut tx = doc.transaction(); + tx.open(); + let root = doc.root(); + let label = root.new_child(); + + let mut builder = TnamingBuilder::new(&tx, &label); + let named_shape; + { + builder.modify(&original, &translated); + + named_shape = builder.named_shape(); + } + + let delta = tx.commit(); + + // After commit: active shape should be the translated one + assert_ne!(named_shape.get().translation(), DVec3::ZERO); + + let _redo = doc.undo(delta); + + // After undo: active shape should be back to original + assert_eq!(named_shape.get().translation(), DVec3::ZERO); + + // original_shape is always the recorded old shape regardless of undo state + let recovered = named_shape.original_shape(); + assert_eq!(recovered.translation(), DVec3::ZERO); + } +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..7e00dbab --- /dev/null +++ b/flake.nix @@ -0,0 +1,93 @@ +{ + description = "Rust development environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + rust-overlay.url = "github:oxalica/rust-overlay"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, rust-overlay, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { inherit system overlays; }; + + rustToolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; + targets = [ "wasm32-unknown-unknown" ]; + }; + + opencascade = pkgs.opencascade-occt; + + # Mesa drivers expose Vulkan and OpenGL ICDs. vulkan-loader is the + # runtime that wgpu uses to discover them via the ICD JSON files. + vulkanPkgs = [ + pkgs.vulkan-loader + pkgs.vulkan-validation-layers + pkgs.mesa + pkgs.mesa.drivers + ]; + in + { + devShells.default = pkgs.mkShell { + buildInputs = [ + # Rust + rustToolchain + pkgs.cargo-watch + + # C/C++ toolchain (fixes "linker `cc` not found") + pkgs.gcc + pkgs.gnumake + pkgs.cmake + pkgs.pkg-config + + # OpenCASCADE (for occt-sys) + opencascade + + # Common occt-sys dependencies + pkgs.freetype + pkgs.fontconfig + pkgs.libGL + pkgs.libGLU + pkgs.libX11 + pkgs.libXcursor + pkgs.libXrandr + pkgs.libXmu + pkgs.libXi + pkgs.libXext + pkgs.libxkbcommon + + # Vulkan / wgpu + ] ++ vulkanPkgs ++ [ + + pkgs.libpng + pkgs.libjpeg + pkgs.libtiff + pkgs.tbb + pkgs.vtk + ]; + + shellHook = '' + export OPENCASCADE_ROOT="${opencascade}" + export PKG_CONFIG_PATH="${opencascade}/lib/pkgconfig:$PKG_CONFIG_PATH" + export LD_LIBRARY_PATH="${opencascade}/lib:${pkgs.freetype}/lib:${pkgs.fontconfig}/lib:${pkgs.libX11}/lib:${pkgs.libXcursor}/lib:${pkgs.libXrandr}/lib:${pkgs.libXi}/lib:${pkgs.libxkbcommon}/lib:${pkgs.vulkan-loader}/lib:${pkgs.mesa}/lib:$LD_LIBRARY_PATH" + export LIBRARY_PATH="${opencascade}/lib:$LIBRARY_PATH" + + # Point the Vulkan loader at Mesa's ICD JSON so it can find the + # actual GPU driver. Without this wgpu sees no backends at all. + export VK_ICD_FILENAMES="${pkgs.mesa.drivers}/share/vulkan/icd.d/intel_icd.x86_64.json:${pkgs.mesa.drivers}/share/vulkan/icd.d/radeon_icd.x86_64.json" + + # Uncomment to force wgpu to use the GL backend via ANGLE/Mesa + # instead of Vulkan — useful if Vulkan still fails: + # export WGPU_BACKEND=gl + + # CMake 4.x removed compatibility with CMakeLists.txt files that + # declare a pre-3.5 minimum version. occt-sys vendors one such file. + # CMAKE_POLICY_VERSION_MINIMUM is read directly by CMake 4.x itself, + # so setting it in the environment is sufficient — no -D flag needed. + export CMAKE_POLICY_VERSION_MINIMUM=3.5 + ''; + }; + }); +}