From 479b6965f744ba11afeedb263630917ceee58bfe Mon Sep 17 00:00:00 2001 From: Alan Panayotov Date: Sat, 17 Aug 2024 17:15:47 +0100 Subject: [PATCH 1/4] chore: split rust into modules --- bindings/src/lib.rs | 284 +------------------------- bindings/src/skinned/export/bone.rs | 28 +++ bindings/src/skinned/export/mod.rs | 18 ++ bindings/src/skinned/export/skl.rs | 162 +++++++++++++++ bindings/src/skinned/export/skn.rs | 43 ++++ bindings/src/skinned/export/vertex.rs | 52 +++++ bindings/src/skinned/mod.rs | 1 + 7 files changed, 311 insertions(+), 277 deletions(-) create mode 100644 bindings/src/skinned/export/bone.rs create mode 100644 bindings/src/skinned/export/mod.rs create mode 100644 bindings/src/skinned/export/skl.rs create mode 100644 bindings/src/skinned/export/skn.rs create mode 100644 bindings/src/skinned/export/vertex.rs create mode 100644 bindings/src/skinned/mod.rs diff --git a/bindings/src/lib.rs b/bindings/src/lib.rs index 71950c4..23ff80d 100644 --- a/bindings/src/lib.rs +++ b/bindings/src/lib.rs @@ -1,294 +1,24 @@ use lib::core::animation::{joint, RigResource}; -use lib::core::mem::{ - IndexBuffer, IndexFormat, VertexBuffer, VertexBufferDescription, VertexBufferUsage, - VertexElement, -}; -use lib::core::mesh::{SkinnedMesh, SkinnedMeshVertexType}; use pyo3::prelude::*; use std::collections::{HashMap, VecDeque}; use std::io::BufWriter; use std::path::PathBuf; +pub mod skinned; + #[pyfunction] fn version() -> PyResult<&'static str> { Ok("1.0.2") } -type Vec3 = [f32; 3]; -type Vec4 = [f32; 4]; -type Mat4 = [[f32; 4]; 4]; - -#[derive(Debug)] -#[pyclass] -struct Vertex { - #[pyo3(get, set)] - pos: Vec3, - #[pyo3(get, set)] - normal: Vec3, - - #[pyo3(get, set)] - blend_indices: [u8; 4], - #[pyo3(get, set)] - blend_weights: [f32; 4], -} - -#[pymethods] -impl Vertex { - #[new] - fn py_new( - pos: Vec3, - normal: Vec3, - blend_indices: Option<[u8; 4]>, - blend_weights: Option<[f32; 4]>, - ) -> Self { - Self { - pos, - normal, - blend_indices: blend_indices.unwrap_or_default(), - blend_weights: blend_weights.unwrap_or_default(), - } - } -} - -impl Vertex { - fn push_bytes(&self, v: &mut Vec) { - // 52 total - for i in self.pos { - // POSITION (12) - v.extend_from_slice(&i.to_le_bytes()) - } - v.extend(self.blend_indices); // BLEND_INDEX (4) - v.extend(self.blend_weights.iter().flat_map(|w| w.to_le_bytes())); // BLEND_WEIGHT (16) - - for i in self.normal { - // NORMAL (12) - v.extend_from_slice(&i.to_le_bytes()) - } - v.extend([0_u8; 8]); // TEXCOORD_0 (8) - } -} - -#[pyfunction] -fn export_skn( - path: PathBuf, - vertices: Vec>, - triangles: Vec<[i64; 3]>, - influences: Option>, -) -> PyResult<()> { - // println!("ve!rts: {vertices:?}"); - - println!("influences: {influences:?}"); - - let mut buf = Vec::new(); - for v in vertices { - v.push_bytes(&mut buf); - } - // println!("{buf:?}"); - let vert_buf = - VertexBufferDescription::from(SkinnedMeshVertexType::Basic).into_vertex_buffer(buf); - // println!("{vert_buf:?}"); - let mut buf = Vec::new(); - - for tri in triangles { - for i in tri { - buf.extend((i as u16).to_le_bytes()); - } - } - - let idx_buf = IndexBuffer::new(IndexFormat::U16, buf); - - let skn = SkinnedMesh::new(vec![], vert_buf, idx_buf); - let mut file = std::fs::File::create(path).map(BufWriter::new).unwrap(); - skn.to_writer(&mut file).unwrap(); - - Ok(()) -} - -#[derive(Debug)] -#[pyclass] -struct Bone { - #[pyo3(get, set)] - parent: Option, - ibm: Mat4, - local: Mat4, - is_influence: bool, -} - -#[pymethods] -impl Bone { - #[new] - fn py_new(parent: String, local: Mat4, ibm: Mat4, is_influence: bool) -> Self { - Self { - parent: match parent.is_empty() { - true => None, - false => Some(parent), - }, - ibm, - local, - is_influence, - } - } -} - -fn topological_sort(graph: &HashMap>) -> Option> { - let mut in_degree: HashMap = HashMap::new(); - let mut zero_in_degree_queue: VecDeque = VecDeque::new(); - let mut sorted_list: Vec = Vec::new(); - - // Initialize in-degree of all nodes to 0 - for node in graph.keys() { - in_degree.insert(node.clone(), 0); - } - - // Calculate in-degree of each node - for children in graph.values() { - for child in children { - if let Some(degree) = in_degree.get_mut(child) { - *degree += 1; - } - } - } - - // Collect nodes with zero in-degree - for (node, °ree) in in_degree.iter() { - if degree == 0 { - zero_in_degree_queue.push_back(node.clone()); - } - } - - // Process nodes with zero in-degree - while let Some(node) = zero_in_degree_queue.pop_front() { - sorted_list.push(node.clone()); - - if let Some(children) = graph.get(&node) { - for child in children { - if let Some(degree) = in_degree.get_mut(child) { - *degree -= 1; - if *degree == 0 { - zero_in_degree_queue.push_back(child.clone()); - } - } - } - } - } - - // If sorted list contains all nodes, return it, otherwise there's a cycle - if sorted_list.len() == graph.len() { - Some(sorted_list) - } else { - None - } -} - -#[pyfunction] -fn export_skl( - bones: HashMap>, - path: Option, -) -> PyResult> { - let mut skl = RigResource::builder("skeleton_name", "skeleton_asset_name"); - - let orig_bone_count = bones.len(); - let mut joints = bones - .into_iter() - .map(|(name, bone)| { - let local_transform = glam::Mat4::from_cols_array_2d(&bone.local).transpose(); - let ibm = glam::Mat4::from_cols_array_2d(&bone.ibm).transpose(); - - println!("mat: {:?}", local_transform.to_scale_rotation_translation()); - ( - name.clone(), - ( - joint::Builder::new(name) - .with_local_transform(local_transform) - .with_inverse_bind_transform(ibm) - .with_influence(bone.is_influence), - bone.parent.clone(), - ), - ) - }) - .collect::>(); - - let mut child_map: HashMap> = joints - .keys() - .map(|name| (name.to_owned(), Vec::new())) - .collect(); - - for (name, (joint, parent)) in &joints { - println!("bone {name:?} -> {parent:?}"); - let Some(parent_map) = parent.clone().and_then(|p| child_map.get_mut(&p)) else { - continue; - }; - parent_map.push(name.clone()); - // skl.add_root_joint(joint.clone().with_children([joint::Builder::new("assas")])); - } - - let mut processed = 0; - let nodes = topological_sort(&child_map).unwrap(); - for n in nodes.iter().rev() { - println!("- {n}"); - let Some(children) = child_map.remove(n) else { - continue; - }; - let children = children - .into_iter() - .filter_map(|c| joints.remove(&c).map(|j| j.0)) - .collect::>(); - println!(" - {children:?}"); - let Some(joint) = joints.get_mut(n) else { - continue; - }; - processed += children.len(); - joint.0.add_children(children); - } - - println!("{processed} child bones."); - println!("{} root bones.", joints.len()); - if orig_bone_count != processed + joints.len() { - println!("[!!] we got {orig_bone_count} bones!"); - } - - for (_, joint) in joints { - skl.add_root_joint(joint.0); - } - - // let root_joints = bones - // .iter() - // .filter_map(|(name, bone)| match bone.parent.is_some() { - // true => None, - // false => { - // joints.insert(name, joint::Builder::new(name)); - // Some(name) - // } - // }) - // .collect::>(); - - // println!("parsed {} bones", joints.len()); - // if orig_bone_count != joints.len() { - // println!("[!!] we got {orig_bone_count} bones!"); - // } - - let rig = skl.build(); - if let Some(path) = path { - let mut file = std::fs::File::create(path).map(BufWriter::new).unwrap(); - rig.to_writer(&mut file).unwrap(); - } - - let joint_map = rig - .influences() - .iter() - .copied() - .map(|i| (rig.joints()[i as usize].name().to_string(), i)) - .collect(); - println!("joint_map: {joint_map:?}"); - Ok(joint_map) -} +pub type Vec3 = [f32; 3]; +pub type Vec4 = [f32; 4]; +pub type Mat4 = [[f32; 4]; 4]; #[pymodule] fn league_toolkit(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - m.add_class::()?; m.add_function(wrap_pyfunction!(version, m)?)?; - m.add_function(wrap_pyfunction!(export_skn, m)?)?; - m.add_function(wrap_pyfunction!(export_skl, m)?)?; + + skinned::export::register(m)?; Ok(()) } diff --git a/bindings/src/skinned/export/bone.rs b/bindings/src/skinned/export/bone.rs new file mode 100644 index 0000000..9532e06 --- /dev/null +++ b/bindings/src/skinned/export/bone.rs @@ -0,0 +1,28 @@ +use crate::Mat4; +use pyo3::prelude::*; + +#[derive(Debug)] +#[pyclass] +pub struct Bone { + #[pyo3(get, set)] + pub parent: Option, + pub ibm: Mat4, + pub local: Mat4, + pub is_influence: bool, +} + +#[pymethods] +impl Bone { + #[new] + fn py_new(parent: String, local: Mat4, ibm: Mat4, is_influence: bool) -> Self { + Self { + parent: match parent.is_empty() { + true => None, + false => Some(parent), + }, + ibm, + local, + is_influence, + } + } +} diff --git a/bindings/src/skinned/export/mod.rs b/bindings/src/skinned/export/mod.rs new file mode 100644 index 0000000..dce4355 --- /dev/null +++ b/bindings/src/skinned/export/mod.rs @@ -0,0 +1,18 @@ +mod bone; +mod skl; +mod skn; +mod vertex; +pub use bone::*; +pub use skl::*; +pub use skn::*; +pub use vertex::*; + +use pyo3::prelude::*; +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_function(wrap_pyfunction!(export_skn, m)?)?; + + m.add_class::()?; + m.add_function(wrap_pyfunction!(export_skl, m)?)?; + Ok(()) +} diff --git a/bindings/src/skinned/export/skl.rs b/bindings/src/skinned/export/skl.rs new file mode 100644 index 0000000..cbc3769 --- /dev/null +++ b/bindings/src/skinned/export/skl.rs @@ -0,0 +1,162 @@ +use lib::core::animation::{joint, RigResource}; +use pyo3::prelude::*; +use std::{ + collections::{HashMap, VecDeque}, + io::BufWriter, + path::PathBuf, +}; + +use super::Bone; + +fn topological_sort(graph: &HashMap>) -> Option> { + let mut in_degree: HashMap = HashMap::new(); + let mut zero_in_degree_queue: VecDeque = VecDeque::new(); + let mut sorted_list: Vec = Vec::new(); + + // Initialize in-degree of all nodes to 0 + for node in graph.keys() { + in_degree.insert(node.clone(), 0); + } + + // Calculate in-degree of each node + for children in graph.values() { + for child in children { + if let Some(degree) = in_degree.get_mut(child) { + *degree += 1; + } + } + } + + // Collect nodes with zero in-degree + for (node, °ree) in in_degree.iter() { + if degree == 0 { + zero_in_degree_queue.push_back(node.clone()); + } + } + + // Process nodes with zero in-degree + while let Some(node) = zero_in_degree_queue.pop_front() { + sorted_list.push(node.clone()); + + if let Some(children) = graph.get(&node) { + for child in children { + if let Some(degree) = in_degree.get_mut(child) { + *degree -= 1; + if *degree == 0 { + zero_in_degree_queue.push_back(child.clone()); + } + } + } + } + } + + // If sorted list contains all nodes, return it, otherwise there's a cycle + if sorted_list.len() == graph.len() { + Some(sorted_list) + } else { + None + } +} + +#[pyfunction] +pub fn export_skl( + bones: HashMap>, + path: Option, +) -> PyResult> { + let mut skl = RigResource::builder("skeleton_name", "skeleton_asset_name"); + + let orig_bone_count = bones.len(); + let mut joints = bones + .into_iter() + .map(|(name, bone)| { + let local_transform = glam::Mat4::from_cols_array_2d(&bone.local).transpose(); + let ibm = glam::Mat4::from_cols_array_2d(&bone.ibm).transpose(); + + println!("mat: {:?}", local_transform.to_scale_rotation_translation()); + ( + name.clone(), + ( + joint::Builder::new(name) + .with_local_transform(local_transform) + .with_inverse_bind_transform(ibm) + .with_influence(bone.is_influence), + bone.parent.clone(), + ), + ) + }) + .collect::>(); + + let mut child_map: HashMap> = joints + .keys() + .map(|name| (name.to_owned(), Vec::new())) + .collect(); + + for (name, (joint, parent)) in &joints { + println!("bone {name:?} -> {parent:?}"); + let Some(parent_map) = parent.clone().and_then(|p| child_map.get_mut(&p)) else { + continue; + }; + parent_map.push(name.clone()); + // skl.add_root_joint(joint.clone().with_children([joint::Builder::new("assas")])); + } + + let mut processed = 0; + let nodes = topological_sort(&child_map).unwrap(); + for n in nodes.iter().rev() { + println!("- {n}"); + let Some(children) = child_map.remove(n) else { + continue; + }; + let children = children + .into_iter() + .filter_map(|c| joints.remove(&c).map(|j| j.0)) + .collect::>(); + println!(" - {children:?}"); + let Some(joint) = joints.get_mut(n) else { + continue; + }; + processed += children.len(); + joint.0.add_children(children); + } + + println!("{processed} child bones."); + println!("{} root bones.", joints.len()); + if orig_bone_count != processed + joints.len() { + println!("[!!] we got {orig_bone_count} bones!"); + } + + for (_, joint) in joints { + skl.add_root_joint(joint.0); + } + + // let root_joints = bones + // .iter() + // .filter_map(|(name, bone)| match bone.parent.is_some() { + // true => None, + // false => { + // joints.insert(name, joint::Builder::new(name)); + // Some(name) + // } + // }) + // .collect::>(); + + // println!("parsed {} bones", joints.len()); + // if orig_bone_count != joints.len() { + // println!("[!!] we got {orig_bone_count} bones!"); + // } + + let rig = skl.build(); + if let Some(path) = path { + let mut file = std::fs::File::create(path).map(BufWriter::new).unwrap(); + rig.to_writer(&mut file).unwrap(); + } + + let joint_map = rig + .influences() + .iter() + .copied() + .map(|i| (rig.joints()[i as usize].name().to_string(), i)) + .collect(); + println!("joint_map: {joint_map:?}"); + Ok(joint_map) +} diff --git a/bindings/src/skinned/export/skn.rs b/bindings/src/skinned/export/skn.rs new file mode 100644 index 0000000..9466067 --- /dev/null +++ b/bindings/src/skinned/export/skn.rs @@ -0,0 +1,43 @@ +use super::Vertex; +use lib::core::{ + mem::{IndexBuffer, IndexFormat, VertexBufferDescription}, + mesh::{SkinnedMesh, SkinnedMeshVertexType}, +}; +use pyo3::prelude::*; +use std::{io::BufWriter, path::PathBuf}; + +#[pyfunction] +pub fn export_skn( + path: PathBuf, + vertices: Vec>, + triangles: Vec<[i64; 3]>, + influences: Option>, +) -> PyResult<()> { + // println!("ve!rts: {vertices:?}"); + + println!("influences: {influences:?}"); + + let mut buf = Vec::new(); + for v in vertices { + v.push_bytes(&mut buf); + } + // println!("{buf:?}"); + let vert_buf = + VertexBufferDescription::from(SkinnedMeshVertexType::Basic).into_vertex_buffer(buf); + // println!("{vert_buf:?}"); + let mut buf = Vec::new(); + + for tri in triangles { + for i in tri { + buf.extend((i as u16).to_le_bytes()); + } + } + + let idx_buf = IndexBuffer::new(IndexFormat::U16, buf); + + let skn = SkinnedMesh::new(vec![], vert_buf, idx_buf); + let mut file = std::fs::File::create(path).map(BufWriter::new).unwrap(); + skn.to_writer(&mut file).unwrap(); + + Ok(()) +} diff --git a/bindings/src/skinned/export/vertex.rs b/bindings/src/skinned/export/vertex.rs new file mode 100644 index 0000000..4df2425 --- /dev/null +++ b/bindings/src/skinned/export/vertex.rs @@ -0,0 +1,52 @@ +use crate::Vec3; +use pyo3::prelude::*; + +#[derive(Debug)] +#[pyclass] +pub struct Vertex { + #[pyo3(get, set)] + pub pos: Vec3, + #[pyo3(get, set)] + pub normal: Vec3, + + #[pyo3(get, set)] + pub blend_indices: [u8; 4], + #[pyo3(get, set)] + pub blend_weights: [f32; 4], +} + +#[pymethods] +impl Vertex { + #[new] + pub fn py_new( + pos: Vec3, + normal: Vec3, + blend_indices: Option<[u8; 4]>, + blend_weights: Option<[f32; 4]>, + ) -> Self { + Self { + pos, + normal, + blend_indices: blend_indices.unwrap_or_default(), + blend_weights: blend_weights.unwrap_or_default(), + } + } +} + +impl Vertex { + pub fn push_bytes(&self, v: &mut Vec) { + // 52 total + for i in self.pos { + // POSITION (12) + v.extend_from_slice(&i.to_le_bytes()) + } + v.extend(self.blend_indices); // BLEND_INDEX (4) + v.extend(self.blend_weights.iter().flat_map(|w| w.to_le_bytes())); // BLEND_WEIGHT (16) + + for i in self.normal { + // NORMAL (12) + v.extend_from_slice(&i.to_le_bytes()) + } + v.extend([0_u8; 8]); // TEXCOORD_0 (8) + } +} diff --git a/bindings/src/skinned/mod.rs b/bindings/src/skinned/mod.rs new file mode 100644 index 0000000..8a42758 --- /dev/null +++ b/bindings/src/skinned/mod.rs @@ -0,0 +1 @@ +pub mod export; From 72f1a380ab9b9825f6140e0fc9beb1e591ff1c31 Mon Sep 17 00:00:00 2001 From: Alan Panayotov Date: Sat, 17 Aug 2024 17:16:26 +0100 Subject: [PATCH 2/4] chore: warning cleanup --- bindings/src/skinned/export/skl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/src/skinned/export/skl.rs b/bindings/src/skinned/export/skl.rs index cbc3769..b73fa2a 100644 --- a/bindings/src/skinned/export/skl.rs +++ b/bindings/src/skinned/export/skl.rs @@ -91,7 +91,7 @@ pub fn export_skl( .map(|name| (name.to_owned(), Vec::new())) .collect(); - for (name, (joint, parent)) in &joints { + for (name, (_, parent)) in &joints { println!("bone {name:?} -> {parent:?}"); let Some(parent_map) = parent.clone().and_then(|p| child_map.get_mut(&p)) else { continue; From a86c21beba2ab68a81d23bd268b2bdee7e868cc0 Mon Sep 17 00:00:00 2001 From: Alan Panayotov Date: Sat, 17 Aug 2024 17:33:41 +0100 Subject: [PATCH 3/4] chore: use print macro (don't print in release mode) --- bindings/src/lib.rs | 9 +++++++++ bindings/src/skinned/export/skl.rs | 21 +++++++++++---------- bindings/src/skinned/export/skn.rs | 8 ++------ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/bindings/src/lib.rs b/bindings/src/lib.rs index 23ff80d..d2bf72d 100644 --- a/bindings/src/lib.rs +++ b/bindings/src/lib.rs @@ -15,6 +15,15 @@ pub type Vec3 = [f32; 3]; pub type Vec4 = [f32; 4]; pub type Mat4 = [[f32; 4]; 4]; +#[macro_export] +macro_rules! debug { + ($($arg:tt)*) => { + #[cfg(debug_assertions)] { + println!($($arg)*); + } + }; +} + #[pymodule] fn league_toolkit(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(version, m)?)?; diff --git a/bindings/src/skinned/export/skl.rs b/bindings/src/skinned/export/skl.rs index b73fa2a..b72a780 100644 --- a/bindings/src/skinned/export/skl.rs +++ b/bindings/src/skinned/export/skl.rs @@ -7,6 +7,7 @@ use std::{ }; use super::Bone; +use crate::debug; fn topological_sort(graph: &HashMap>) -> Option> { let mut in_degree: HashMap = HashMap::new(); @@ -72,7 +73,7 @@ pub fn export_skl( let local_transform = glam::Mat4::from_cols_array_2d(&bone.local).transpose(); let ibm = glam::Mat4::from_cols_array_2d(&bone.ibm).transpose(); - println!("mat: {:?}", local_transform.to_scale_rotation_translation()); + debug!("mat: {:?}", local_transform.to_scale_rotation_translation()); ( name.clone(), ( @@ -92,7 +93,7 @@ pub fn export_skl( .collect(); for (name, (_, parent)) in &joints { - println!("bone {name:?} -> {parent:?}"); + debug!("bone {name:?} -> {parent:?}"); let Some(parent_map) = parent.clone().and_then(|p| child_map.get_mut(&p)) else { continue; }; @@ -103,7 +104,7 @@ pub fn export_skl( let mut processed = 0; let nodes = topological_sort(&child_map).unwrap(); for n in nodes.iter().rev() { - println!("- {n}"); + debug!("- {n}"); let Some(children) = child_map.remove(n) else { continue; }; @@ -111,7 +112,7 @@ pub fn export_skl( .into_iter() .filter_map(|c| joints.remove(&c).map(|j| j.0)) .collect::>(); - println!(" - {children:?}"); + debug!(" - {children:?}"); let Some(joint) = joints.get_mut(n) else { continue; }; @@ -119,10 +120,10 @@ pub fn export_skl( joint.0.add_children(children); } - println!("{processed} child bones."); - println!("{} root bones.", joints.len()); + debug!("{processed} child bones."); + debug!("{} root bones.", joints.len()); if orig_bone_count != processed + joints.len() { - println!("[!!] we got {orig_bone_count} bones!"); + debug!("[!!] we got {orig_bone_count} bones!"); } for (_, joint) in joints { @@ -140,9 +141,9 @@ pub fn export_skl( // }) // .collect::>(); - // println!("parsed {} bones", joints.len()); + // debug!("parsed {} bones", joints.len()); // if orig_bone_count != joints.len() { - // println!("[!!] we got {orig_bone_count} bones!"); + // debug!("[!!] we got {orig_bone_count} bones!"); // } let rig = skl.build(); @@ -157,6 +158,6 @@ pub fn export_skl( .copied() .map(|i| (rig.joints()[i as usize].name().to_string(), i)) .collect(); - println!("joint_map: {joint_map:?}"); + debug!("joint_map: {joint_map:?}"); Ok(joint_map) } diff --git a/bindings/src/skinned/export/skn.rs b/bindings/src/skinned/export/skn.rs index 9466067..bf46535 100644 --- a/bindings/src/skinned/export/skn.rs +++ b/bindings/src/skinned/export/skn.rs @@ -13,18 +13,14 @@ pub fn export_skn( triangles: Vec<[i64; 3]>, influences: Option>, ) -> PyResult<()> { - // println!("ve!rts: {vertices:?}"); - - println!("influences: {influences:?}"); - let mut buf = Vec::new(); for v in vertices { v.push_bytes(&mut buf); } - // println!("{buf:?}"); + // debug!("{buf:?}"); let vert_buf = VertexBufferDescription::from(SkinnedMeshVertexType::Basic).into_vertex_buffer(buf); - // println!("{vert_buf:?}"); + // debug!("{vert_buf:?}"); let mut buf = Vec::new(); for tri in triangles { From cc01a4244da05c3d2fa3ac31106579d4dc3cfc15 Mon Sep 17 00:00:00 2001 From: Alan Panayotov Date: Sat, 17 Aug 2024 18:46:25 +0100 Subject: [PATCH 4/4] feat: abi3 --- bindings/Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bindings/Cargo.toml b/bindings/Cargo.toml index d15f526..8355281 100644 --- a/bindings/Cargo.toml +++ b/bindings/Cargo.toml @@ -9,4 +9,8 @@ crate-type = ["cdylib"] [dependencies] glam = { version = "0.27.0", features = ["glam-assert"] } lib = { version = "0.1.0", path = "../league-toolkit/crates/league-toolkit", package = "league-toolkit" } -pyo3 = { version = "0.21.2", features = ["extension-module"] } +pyo3 = { version = "0.21.2", features = [ + "extension-module", + "abi3", + "abi3-py310", +] }