unitforge 0.3.11__tar.gz → 0.3.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. {unitforge-0.3.11 → unitforge-0.3.13}/.gitignore +3 -0
  2. {unitforge-0.3.11 → unitforge-0.3.13}/Cargo.lock +2 -2
  3. {unitforge-0.3.11 → unitforge-0.3.13}/Cargo.toml +2 -2
  4. {unitforge-0.3.11 → unitforge-0.3.13}/PKG-INFO +1 -1
  5. {unitforge-0.3.11 → unitforge-0.3.13}/pyproject.toml +4 -0
  6. unitforge-0.3.13/python/unitforge/__init__.py +4 -0
  7. unitforge-0.3.13/src/quantities.template.rs +624 -0
  8. {unitforge-0.3.11 → unitforge-0.3.13}/unitforge_macros/Cargo.toml +1 -1
  9. {unitforge-0.3.11 → unitforge-0.3.13}/unitforge_macros/src/lib.rs +104 -4
  10. unitforge-0.3.11/python/unitforge/__init__.py +0 -5
  11. unitforge-0.3.11/src/quantities.template.rs +0 -240
  12. {unitforge-0.3.11 → unitforge-0.3.13}/.gitlab-ci.yml +0 -0
  13. {unitforge-0.3.11 → unitforge-0.3.13}/Readme.md +0 -0
  14. {unitforge-0.3.11 → unitforge-0.3.13}/Readme_py.md +0 -0
  15. {unitforge-0.3.11 → unitforge-0.3.13}/scripts/generate_python_stubs.py +0 -0
  16. {unitforge-0.3.11 → unitforge-0.3.13}/src/impl_macros.rs +0 -0
  17. {unitforge-0.3.11 → unitforge-0.3.13}/src/lib.rs +0 -0
  18. {unitforge-0.3.11 → unitforge-0.3.13}/src/prelude.rs +0 -0
  19. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/acceleration.rs +0 -0
  20. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/angle.rs +0 -0
  21. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/angular_acceleration.rs +0 -0
  22. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/angular_velocity.rs +0 -0
  23. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/area.rs +0 -0
  24. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/area_of_moment.rs +0 -0
  25. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/charge.rs +0 -0
  26. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/compliance.rs +0 -0
  27. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/density.rs +0 -0
  28. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/distance.rs +0 -0
  29. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/force.rs +0 -0
  30. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/force_area.rs +0 -0
  31. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/force_distance.rs +0 -0
  32. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/force_div_distance_power_four.rs +0 -0
  33. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/force_per_volume.rs +0 -0
  34. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/force_stress.rs +0 -0
  35. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/force_volume.rs +0 -0
  36. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/inverse_area.rs +0 -0
  37. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/inverse_distance.rs +0 -0
  38. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/inverse_stress.rs +0 -0
  39. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/mass.rs +0 -0
  40. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/mass_div_time_squared_volume.rs +0 -0
  41. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/mass_per_distance.rs +0 -0
  42. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/rotational_stiffness.rs +0 -0
  43. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/stiffness.rs +0 -0
  44. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/strain.rs +0 -0
  45. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/stress.rs +0 -0
  46. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/stress_squared.rs +0 -0
  47. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/time.rs +0 -0
  48. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/velocity.rs +0 -0
  49. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/velocity_squared.rs +0 -0
  50. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/voltage.rs +0 -0
  51. {unitforge-0.3.11 → unitforge-0.3.13}/src/quantities/volume.rs +0 -0
  52. {unitforge-0.3.11 → unitforge-0.3.13}/src/relations.rs +0 -0
  53. {unitforge-0.3.11 → unitforge-0.3.13}/src/small_linalg/bindings/matrix3_py.template.rs +0 -0
  54. {unitforge-0.3.11 → unitforge-0.3.13}/src/small_linalg/bindings/vector3_py.template.rs +0 -0
  55. {unitforge-0.3.11 → unitforge-0.3.13}/src/small_linalg/matrix2.rs +0 -0
  56. {unitforge-0.3.11 → unitforge-0.3.13}/src/small_linalg/matrix2x3.rs +0 -0
  57. {unitforge-0.3.11 → unitforge-0.3.13}/src/small_linalg/matrix3.rs +0 -0
  58. {unitforge-0.3.11 → unitforge-0.3.13}/src/small_linalg/matrix3x2.rs +0 -0
  59. {unitforge-0.3.11 → unitforge-0.3.13}/src/small_linalg/vector2.rs +0 -0
  60. {unitforge-0.3.11 → unitforge-0.3.13}/src/small_linalg/vector3.rs +0 -0
  61. {unitforge-0.3.11 → unitforge-0.3.13}/src/tests/quantity_unit_tests.rs +0 -0
  62. {unitforge-0.3.11 → unitforge-0.3.13}/src/tests/serde_tests.rs +0 -0
  63. {unitforge-0.3.11 → unitforge-0.3.13}/tests/matrix2.rs +0 -0
  64. {unitforge-0.3.11 → unitforge-0.3.13}/tests/matrix2x3.rs +0 -0
  65. {unitforge-0.3.11 → unitforge-0.3.13}/tests/matrix3.rs +0 -0
  66. {unitforge-0.3.11 → unitforge-0.3.13}/tests/matrix3x2.rs +0 -0
  67. {unitforge-0.3.11 → unitforge-0.3.13}/tests/ndarray_dot.rs +0 -0
  68. {unitforge-0.3.11 → unitforge-0.3.13}/tests/quantities.rs +0 -0
  69. {unitforge-0.3.11 → unitforge-0.3.13}/tests/small_linalg_interfaces.rs +0 -0
  70. {unitforge-0.3.11 → unitforge-0.3.13}/tests/test_quantities.py +0 -0
  71. {unitforge-0.3.11 → unitforge-0.3.13}/tests/test_small_linalg.py +0 -0
  72. {unitforge-0.3.11 → unitforge-0.3.13}/tests/typecheck/mypy_smoke.py +0 -0
  73. {unitforge-0.3.11 → unitforge-0.3.13}/tests/vector2.rs +0 -0
  74. {unitforge-0.3.11 → unitforge-0.3.13}/tests/vector3.rs +0 -0
@@ -1,6 +1,9 @@
1
1
  debug/
2
2
  target/
3
3
  dist/
4
+ python/unitforge/__init__.pyi
5
+ python/unitforge/py.typed
6
+ python/unitforge/unitforge*
4
7
  **/*.rs.bk
5
8
  .idea/
6
9
  .gitlab-ci-local/
@@ -369,7 +369,7 @@ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
369
369
 
370
370
  [[package]]
371
371
  name = "unitforge"
372
- version = "0.3.11"
372
+ version = "0.3.13"
373
373
  dependencies = [
374
374
  "ndarray",
375
375
  "num-traits",
@@ -384,7 +384,7 @@ dependencies = [
384
384
 
385
385
  [[package]]
386
386
  name = "unitforge_macros"
387
- version = "0.3.11"
387
+ version = "0.3.13"
388
388
  dependencies = [
389
389
  "regex",
390
390
  ]
@@ -4,7 +4,7 @@ crate-type = ["cdylib", "rlib"]
4
4
 
5
5
  [package]
6
6
  name = "unitforge"
7
- version = "0.3.11"
7
+ version = "0.3.13"
8
8
  edition = "2021"
9
9
  authors = ["Dr.Q team <info@askdrq.com>", "Henrik Stromberg <henrik@askdrq.com>"]
10
10
  description = "A library for unit and quantity consistent computations in Rust"
@@ -23,7 +23,7 @@ pyo3 = { version = "0.26.0", features = ["extension-module", "auto-initialize",
23
23
  numpy = { version = "0.26.0", optional = true }
24
24
  strum = { version = "0.27.2", optional = true }
25
25
  strum_macros = { version = "0.27.2", optional = true }
26
- unitforge_macros = { version = "0.3.10", path = "unitforge_macros" }
26
+ unitforge_macros = { version = "0.3.13", path = "unitforge_macros" }
27
27
 
28
28
  [features]
29
29
  serde = ["dep:serde", "dep:serde_json", "ndarray/serde"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitforge
3
- Version: 0.3.11
3
+ Version: 0.3.13
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -34,3 +34,7 @@ bindings = "pyo3"
34
34
  features = ["pyo3"]
35
35
  python-source = "python"
36
36
  module-name = "unitforge.unitforge"
37
+ include = [
38
+ "unitforge/__init__.pyi",
39
+ "unitforge/py.typed",
40
+ ]
@@ -0,0 +1,4 @@
1
+ from .unitforge import * # noqa: F401,F403
2
+ from . import unitforge as _ext
3
+ _UNITFORGE_API = _ext._UNITFORGE_API
4
+ del _ext
@@ -0,0 +1,624 @@
1
+ use std::fmt::Display;
2
+ use std::cmp::Ordering;
3
+ use std::fmt;
4
+ use std::fmt::Formatter;
5
+ use std::ops::{Add, Sub, Mul, Div, Neg};
6
+ #[cfg(feature = "pyo3")]
7
+ use pyo3::{Bound, PyAny, prelude::*};
8
+
9
+ #[derive(Debug)]
10
+ pub enum QuantityOperationError {
11
+ AddError,
12
+ SubError,
13
+ MulError,
14
+ DivError,
15
+ SqrtError,
16
+ ComparisonError,
17
+ }
18
+
19
+ impl fmt::Display for QuantityOperationError {
20
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21
+ match self {
22
+ QuantityOperationError::AddError => write!(f, "Addition operation failed"),
23
+ QuantityOperationError::SubError => write!(f, "Subtraction operation failed"),
24
+ QuantityOperationError::MulError => write!(f, "Multiplication operation failed"),
25
+ QuantityOperationError::DivError => write!(f, "Division operation failed"),
26
+ QuantityOperationError::SqrtError => write!(f, "Sqrt operation failed"),
27
+ QuantityOperationError::ComparisonError => write!(f, "Comparison operation failed"),
28
+ }
29
+ }
30
+ }
31
+
32
+ #[derive(Clone, Copy, PartialEq, Debug)]
33
+ pub enum Quantity {
34
+ //Is used for runtime checked operations with quantities
35
+ FloatQuantity(f64),
36
+ // __QUANTITY_VARIANTS__
37
+ }
38
+
39
+ impl Quantity {
40
+ pub fn to(&self, unit: Unit) -> Result<f64, String> {
41
+ match (self, unit) {
42
+ (Quantity::FloatQuantity(value), Unit::NoUnit) => Ok(*value),
43
+ // __QUANTITY_TO_VARIANTS__
44
+ _ => Err("Cannot use given pair of quantity and unit.".to_string())
45
+ }
46
+ }
47
+
48
+ pub fn abs(&self) -> Quantity {
49
+ match self {
50
+ Quantity::FloatQuantity(value) => Quantity::FloatQuantity(value.abs()),
51
+ // __QUANTITY_ABS_VARIANTS__
52
+ }
53
+ }
54
+
55
+ pub fn is_nan(&self) -> bool {
56
+ match self {
57
+ Quantity::FloatQuantity(value) => value.is_nan(),
58
+ // __QUANTITY_NAN_VARIANTS__
59
+ }
60
+ }
61
+ }
62
+
63
+
64
+ impl Neg for Quantity {
65
+ type Output = Quantity;
66
+ fn neg(self) -> Quantity {
67
+ match self {
68
+ Quantity::FloatQuantity(value) => Quantity::FloatQuantity(-value),
69
+ // __QUANTITY_NEG_VARIANTS__
70
+ }
71
+ }
72
+ }
73
+
74
+
75
+ #[derive(Clone, Copy, PartialEq, Debug)]
76
+ pub enum Unit {
77
+ //Is used for runtime checked operations with quantities
78
+ NoUnit,
79
+ // __UNIT_VARIANTS__
80
+ }
81
+
82
+ impl Display for Unit {
83
+ fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
84
+ write!(f, "{}", self.get_name())
85
+ }
86
+ }
87
+
88
+ impl Unit {
89
+ pub fn to_quantity(&self, value: f64) -> Quantity {
90
+ match self {
91
+ Unit::NoUnit => Quantity::FloatQuantity(value),
92
+ // __TO_QUANTITY_VARIANTS__
93
+ }
94
+ }
95
+
96
+ pub fn get_name(&self) -> &str {
97
+ match self {
98
+ Unit::NoUnit => "No Unit",
99
+ // __TO_UNIT_NAME_VARIANTS__
100
+ }
101
+ }
102
+ }
103
+
104
+ impl Mul for Quantity {
105
+ type Output = Result<Quantity, QuantityOperationError>;
106
+ fn mul(self, other: Quantity) -> Result<Quantity, QuantityOperationError> {
107
+ fn try_multiply(lhs: &Quantity, rhs: &Quantity) -> Result<Quantity, QuantityOperationError> {
108
+ use Quantity::*;
109
+ match (lhs, rhs) {
110
+ (FloatQuantity(v_lhs), FloatQuantity(v_rhs)) => Ok(FloatQuantity(v_lhs * v_rhs)),
111
+ // __MUL_MATCHES__
112
+ _ => Err(QuantityOperationError::MulError)
113
+ }
114
+ }
115
+ match try_multiply(&self, &other) {
116
+ Ok(result) => Ok(result),
117
+ Err(_) => try_multiply(&other, &self)
118
+ }
119
+ }
120
+ }
121
+
122
+ impl Div for Quantity {
123
+ type Output = Result<Quantity, QuantityOperationError>;
124
+ fn div(self, other: Quantity) -> Result<Quantity, QuantityOperationError> {
125
+ use Quantity::*;
126
+ match (self, other) {
127
+ (FloatQuantity(v_lhs), FloatQuantity(v_rhs)) => Ok(FloatQuantity(v_lhs / v_rhs)),
128
+ // __DIV_MATCHES__
129
+ _ => Err(QuantityOperationError::DivError)
130
+ }
131
+ }
132
+ }
133
+
134
+ impl Add for Quantity {
135
+ type Output = Result<Quantity, QuantityOperationError>;
136
+ fn add(self, other: Quantity) -> Result<Quantity, QuantityOperationError> {
137
+ use Quantity::*;
138
+ match (self, other) {
139
+ (Quantity::FloatQuantity(v_lhs), Quantity::FloatQuantity(v_rhs)) => Ok(Quantity::FloatQuantity(v_lhs + v_rhs)),
140
+ // __ADD_QUANTITY_MATCHES__
141
+ _ => Err(QuantityOperationError::AddError)
142
+ }
143
+ }
144
+ }
145
+
146
+ impl Sub for Quantity {
147
+ type Output = Result<Quantity, QuantityOperationError>;
148
+ fn sub(self, other: Quantity) -> Result<Self, QuantityOperationError> {
149
+ use Quantity::*;
150
+ match (self, other) {
151
+ (Quantity::FloatQuantity(v_lhs), Quantity::FloatQuantity(v_rhs)) => Ok(Quantity::FloatQuantity(v_lhs - v_rhs)),
152
+ // __SUB_QUANTITY_MATCHES__
153
+ _ => Err(QuantityOperationError::SubError)
154
+ }
155
+ }
156
+ }
157
+
158
+ impl Quantity {
159
+ pub fn extract_float(&self) -> Result<f64, String> {
160
+ match self {
161
+ Quantity::FloatQuantity(v) => Ok(*v),
162
+ _ => Err("Cannot extract float from Quantity enum".into()),
163
+ }
164
+ }
165
+
166
+ // __BASE_QUANTITY_MATCHES__
167
+ pub fn sqrt(&self) -> Result<Self, QuantityOperationError> {
168
+ match self {
169
+ Quantity::FloatQuantity(v) => Ok(Self::FloatQuantity(v.sqrt())),
170
+ // __QUANTITY_SQRTS__
171
+ _=> Err(QuantityOperationError::SqrtError)
172
+ }
173
+ }
174
+ }
175
+
176
+ impl PartialOrd for Quantity {
177
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
178
+ use Quantity::*;
179
+ match (self, other) {
180
+ (FloatQuantity(lhs), FloatQuantity(rhs)) => lhs.partial_cmp(rhs),
181
+ // __QUANTITY_COMPARISONS__
182
+ _ => panic!("Cannot compare non matching quantities!")
183
+ }
184
+ }
185
+ }
186
+
187
+ #[cfg(feature = "pyo3")]
188
+ fn extract_f64(v: &Bound<PyAny>) -> Option<f64> {
189
+ if let Ok(inner) = v.extract::<f64>() {
190
+ Some(inner)
191
+ } else if let Ok(inner) = v.extract::<f32>() {
192
+ Some(inner as f64)
193
+ } else if let Ok(inner) = v.extract::<i32>() {
194
+ Some(inner as f64)
195
+ } else if let Ok(inner) = v.extract::<i64>() {
196
+ Some(inner as f64)
197
+ } else {
198
+ None
199
+ }
200
+ }
201
+
202
+ #[cfg(feature = "pyo3")]
203
+ impl Quantity {
204
+ pub fn from_py_any(v: &Bound<PyAny>) -> Result<Self, String> {
205
+ if let Some(inner) = extract_f64(v) {
206
+ Ok(Quantity::FloatQuantity(inner))
207
+ }
208
+ // __EXTRACT_QUANTITY_MATCHES__
209
+ else if let Ok(inner) = pyo3_capsule_api::extract_quantity_from_capsule(v) {
210
+ Ok(inner)
211
+ }
212
+ else {
213
+ Err("Cannot interpret given value as Quantity".to_string())
214
+ }
215
+ }
216
+
217
+ pub fn to_pyobject(self, py: Python) -> PyResult<Py<PyAny>> {
218
+ Ok(match self {
219
+ Quantity::FloatQuantity(v) => v.into_pyobject(py).map(|obj| obj.into())?,
220
+ // __TO_PYOBJECT_MATCHES__
221
+ })
222
+ }
223
+ }
224
+
225
+ impl Display for Quantity {
226
+ fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
227
+ match self {
228
+ Quantity::FloatQuantity(v) => write!(f, "{v}"),
229
+ // __QUANTITY_FMT_MATCHES__
230
+ }
231
+ }
232
+ }
233
+
234
+ #[cfg(feature = "pyo3")]
235
+ impl Unit {
236
+
237
+ pub fn from_py_any(v: &Bound<PyAny>) -> Result<Self, String> {
238
+ // __EXTRACT_UNIT_MATCHES__
239
+ else if let Ok(inner) = pyo3_capsule_api::extract_unit_from_capsule(v) {
240
+ Ok(inner)
241
+ }
242
+ else {
243
+ Err("Cannot interpret given value as Quantity".to_string())
244
+ }
245
+ }
246
+ }
247
+
248
+ #[cfg(feature = "pyo3")]
249
+ pub mod pyo3_capsule_api {
250
+ use super::*;
251
+ use pyo3::exceptions::PyValueError;
252
+ use pyo3::ffi;
253
+ use pyo3::types::{PyAnyMethods, PyCapsule, PyCapsuleMethods, PyList, PyModule};
254
+ use pyo3::{Bound, PyErr, PyRef, PyResult, Python};
255
+ use std::ffi::{c_int, CString};
256
+
257
+ pub const UNITFORGE_API_VERSION: u64 = 1;
258
+ pub const CAPSULE_NAME: &str = "_UNITFORGE_API";
259
+
260
+ #[repr(C)]
261
+ #[derive(Clone, Copy, Debug)]
262
+ pub struct QuantityRepr {
263
+ pub kind_id: u32,
264
+ pub multiplier: f64,
265
+ pub power: i32,
266
+ }
267
+
268
+ #[repr(C)]
269
+ #[derive(Clone, Copy, Debug)]
270
+ pub struct UnitRepr {
271
+ pub kind_id: u32,
272
+ pub variant_id: i32,
273
+ }
274
+
275
+ pub trait QuantityKindId {
276
+ const KIND_ID: u32;
277
+ }
278
+
279
+ pub trait UnitKindId {
280
+ const KIND_ID: u32;
281
+ }
282
+
283
+ pub trait UnitVariantId {
284
+ fn to_variant_id(self) -> i32;
285
+ fn from_variant_id(variant_id: i32) -> Option<Self>
286
+ where
287
+ Self: Sized;
288
+ }
289
+
290
+ // __QUANTITY_KIND_ID_IMPLS__
291
+ // __UNIT_KIND_ID_IMPLS__
292
+ // __UNIT_VARIANT_ID_IMPLS__
293
+
294
+ pub fn quantity_to_repr(quantity: Quantity) -> Result<QuantityRepr, String> {
295
+ match quantity {
296
+ Quantity::FloatQuantity(value) => Ok(QuantityRepr {
297
+ kind_id: 0,
298
+ multiplier: value,
299
+ power: 0,
300
+ }),
301
+ // __QUANTITY_TO_REPR_MATCHES__
302
+ }
303
+ }
304
+
305
+ fn quantity_from_repr(repr: QuantityRepr) -> Result<Quantity, String> {
306
+ match repr.kind_id {
307
+ 0 => Ok(Quantity::FloatQuantity(repr.multiplier * 10f64.powi(repr.power))),
308
+ // __QUANTITY_FROM_REPR_MATCHES__
309
+ _ => Err(format!("Unknown quantity kind id: {}", repr.kind_id)),
310
+ }
311
+ }
312
+
313
+ pub fn unit_to_repr(unit: Unit) -> UnitRepr {
314
+ match unit {
315
+ Unit::NoUnit => UnitRepr {
316
+ kind_id: 0,
317
+ variant_id: 0,
318
+ },
319
+ // __UNIT_TO_REPR_MATCHES__
320
+ }
321
+ }
322
+
323
+ fn unit_from_repr(repr: UnitRepr) -> Result<Unit, String> {
324
+ match repr.kind_id {
325
+ 0 => Ok(Unit::NoUnit),
326
+ // __UNIT_FROM_REPR_MATCHES__
327
+ _ => Err(format!("Unknown unit kind id: {}", repr.kind_id)),
328
+ }
329
+ }
330
+
331
+ pub fn extract_quantity_from_capsule(obj: &Bound<PyAny>) -> PyResult<Quantity> {
332
+ let py = obj.py();
333
+ let module = PyModule::import(py, "unitforge")?;
334
+ let api = get_api(py, &module)?;
335
+ let mut repr = QuantityRepr {
336
+ kind_id: 0,
337
+ multiplier: 0.0,
338
+ power: 0,
339
+ };
340
+ // SAFETY: `obj` is a valid python object and `repr` points to writable memory.
341
+ let rc = unsafe { (api.extract_quantity_repr)(obj.as_ptr(), &mut repr as *mut QuantityRepr) };
342
+ if rc != 0 {
343
+ return Err(PyErr::fetch(py));
344
+ }
345
+ quantity_from_repr(repr).map_err(PyValueError::new_err)
346
+ }
347
+
348
+ pub fn extract_unit_from_capsule(obj: &Bound<PyAny>) -> PyResult<Unit> {
349
+ let py = obj.py();
350
+ let module = PyModule::import(py, "unitforge")?;
351
+ let api = get_api(py, &module)?;
352
+ let mut repr = UnitRepr {
353
+ kind_id: 0,
354
+ variant_id: 0,
355
+ };
356
+ // SAFETY: `obj` is a valid python object and `repr` points to writable memory.
357
+ let rc = unsafe { (api.extract_unit_repr)(obj.as_ptr(), &mut repr as *mut UnitRepr) };
358
+ if rc != 0 {
359
+ return Err(PyErr::fetch(py));
360
+ }
361
+ unit_from_repr(repr).map_err(PyValueError::new_err)
362
+ }
363
+
364
+ #[repr(C)]
365
+ #[derive(Clone, Copy)]
366
+ pub struct UnitforgeApiV1 {
367
+ pub version: u64,
368
+ pub extract_quantity_repr:
369
+ unsafe extern "C" fn(obj: *mut ffi::PyObject, out: *mut QuantityRepr) -> c_int,
370
+ pub quantity_repr_to_object:
371
+ unsafe extern "C" fn(repr: QuantityRepr) -> *mut ffi::PyObject,
372
+ pub extract_unit_repr:
373
+ unsafe extern "C" fn(obj: *mut ffi::PyObject, out: *mut UnitRepr) -> c_int,
374
+ pub unit_repr_to_object: unsafe extern "C" fn(repr: UnitRepr) -> *mut ffi::PyObject,
375
+ pub extract_vector3_repr:
376
+ unsafe extern "C" fn(obj: *mut ffi::PyObject, out: *mut QuantityRepr) -> c_int,
377
+ pub vector3_repr_to_object:
378
+ unsafe extern "C" fn(data: *const QuantityRepr) -> *mut ffi::PyObject,
379
+ pub extract_matrix3_repr:
380
+ unsafe extern "C" fn(obj: *mut ffi::PyObject, out: *mut QuantityRepr) -> c_int,
381
+ pub matrix3_repr_to_object:
382
+ unsafe extern "C" fn(data: *const QuantityRepr) -> *mut ffi::PyObject,
383
+ }
384
+
385
+ unsafe extern "C" fn extract_quantity_repr_impl(
386
+ obj: *mut ffi::PyObject,
387
+ out: *mut QuantityRepr,
388
+ ) -> c_int {
389
+ let py = Python::assume_attached();
390
+ let result: PyResult<()> = (|| {
391
+ if out.is_null() {
392
+ return Err(PyValueError::new_err("output pointer for quantity repr is null"));
393
+ }
394
+ let obj_bound = Bound::from_borrowed_ptr(py, obj);
395
+ let quantity = Quantity::from_py_any(&obj_bound).map_err(PyValueError::new_err)?;
396
+ unsafe {
397
+ *out = quantity_to_repr(quantity).map_err(PyValueError::new_err)?;
398
+ }
399
+ Ok(())
400
+ })();
401
+
402
+ match result {
403
+ Ok(_) => 0,
404
+ Err(err) => {
405
+ err.restore(py);
406
+ -1
407
+ }
408
+ }
409
+ }
410
+
411
+ unsafe extern "C" fn quantity_repr_to_object_impl(repr: QuantityRepr) -> *mut ffi::PyObject {
412
+ let py = Python::assume_attached();
413
+ let result: PyResult<Py<PyAny>> = (|| {
414
+ quantity_from_repr(repr)
415
+ .map_err(PyValueError::new_err)?
416
+ .to_pyobject(py)
417
+ })();
418
+ match result {
419
+ Ok(obj) => obj.into_ptr(),
420
+ Err(err) => {
421
+ err.restore(py);
422
+ std::ptr::null_mut()
423
+ }
424
+ }
425
+ }
426
+
427
+ unsafe extern "C" fn extract_unit_repr_impl(
428
+ obj: *mut ffi::PyObject,
429
+ out: *mut UnitRepr,
430
+ ) -> c_int {
431
+ let py = Python::assume_attached();
432
+ let result: PyResult<()> = (|| {
433
+ if out.is_null() {
434
+ return Err(PyValueError::new_err("output pointer for unit repr is null"));
435
+ }
436
+ let obj_bound = Bound::from_borrowed_ptr(py, obj);
437
+ let unit = Unit::from_py_any(&obj_bound).map_err(PyValueError::new_err)?;
438
+ unsafe {
439
+ *out = unit_to_repr(unit);
440
+ }
441
+ Ok(())
442
+ })();
443
+
444
+ match result {
445
+ Ok(_) => 0,
446
+ Err(err) => {
447
+ err.restore(py);
448
+ -1
449
+ }
450
+ }
451
+ }
452
+
453
+ unsafe extern "C" fn unit_repr_to_object_impl(repr: UnitRepr) -> *mut ffi::PyObject {
454
+ let py = Python::assume_attached();
455
+ let result: PyResult<Py<PyAny>> = (|| {
456
+ let unit = unit_from_repr(repr).map_err(PyValueError::new_err)?;
457
+ match unit {
458
+ Unit::NoUnit => {
459
+ Err(PyValueError::new_err("NoUnit cannot be converted into a Python object"))
460
+ }
461
+ // __UNIT_TO_PYOBJECT_MATCHES__
462
+ }
463
+ })();
464
+ match result {
465
+ Ok(obj) => obj.into_ptr(),
466
+ Err(err) => {
467
+ err.restore(py);
468
+ std::ptr::null_mut()
469
+ }
470
+ }
471
+ }
472
+
473
+ unsafe extern "C" fn extract_vector3_repr_impl(
474
+ obj: *mut ffi::PyObject,
475
+ out: *mut QuantityRepr,
476
+ ) -> c_int {
477
+ let py = Python::assume_attached();
478
+ let result: PyResult<()> = (|| {
479
+ if out.is_null() {
480
+ return Err(PyValueError::new_err("output pointer for vector repr is null"));
481
+ }
482
+ let obj_bound = Bound::from_borrowed_ptr(py, obj);
483
+ let vec = obj_bound.extract::<Vector3Py>()?;
484
+ for i in 0..3 {
485
+ let repr = quantity_to_repr(vec.data[i]).map_err(PyValueError::new_err)?;
486
+ unsafe {
487
+ *out.add(i) = repr;
488
+ }
489
+ }
490
+ Ok(())
491
+ })();
492
+ match result {
493
+ Ok(_) => 0,
494
+ Err(err) => {
495
+ err.restore(py);
496
+ -1
497
+ }
498
+ }
499
+ }
500
+
501
+ unsafe extern "C" fn vector3_repr_to_object_impl(
502
+ data: *const QuantityRepr,
503
+ ) -> *mut ffi::PyObject {
504
+ let py = Python::assume_attached();
505
+ let result: PyResult<Py<PyAny>> = (|| {
506
+ if data.is_null() {
507
+ return Err(PyValueError::new_err("input pointer for vector repr is null"));
508
+ }
509
+ let mut quantity_data = [Quantity::FloatQuantity(0.0); 3];
510
+ for i in 0..3 {
511
+ let repr = unsafe { *data.add(i) };
512
+ quantity_data[i] = quantity_from_repr(repr).map_err(PyValueError::new_err)?;
513
+ }
514
+ let vec = Vector3Py { data: quantity_data };
515
+ Ok(vec.into_pyobject(py)?.into_any().unbind())
516
+ })();
517
+
518
+ match result {
519
+ Ok(obj) => obj.into_ptr(),
520
+ Err(err) => {
521
+ err.restore(py);
522
+ std::ptr::null_mut()
523
+ }
524
+ }
525
+ }
526
+
527
+ unsafe extern "C" fn extract_matrix3_repr_impl(
528
+ obj: *mut ffi::PyObject,
529
+ out: *mut QuantityRepr,
530
+ ) -> c_int {
531
+ let py = Python::assume_attached();
532
+ let result: PyResult<()> = (|| {
533
+ if out.is_null() {
534
+ return Err(PyValueError::new_err("output pointer for matrix repr is null"));
535
+ }
536
+ let obj_bound = Bound::from_borrowed_ptr(py, obj);
537
+ let mat = obj_bound.extract::<PyRef<Matrix3Py>>()?;
538
+ for i in 0..3 {
539
+ for j in 0..3 {
540
+ let repr =
541
+ quantity_to_repr(mat.data[i][j]).map_err(PyValueError::new_err)?;
542
+ unsafe {
543
+ *out.add(i * 3 + j) = repr;
544
+ }
545
+ }
546
+ }
547
+ Ok(())
548
+ })();
549
+ match result {
550
+ Ok(_) => 0,
551
+ Err(err) => {
552
+ err.restore(py);
553
+ -1
554
+ }
555
+ }
556
+ }
557
+
558
+ unsafe extern "C" fn matrix3_repr_to_object_impl(
559
+ data: *const QuantityRepr,
560
+ ) -> *mut ffi::PyObject {
561
+ let py = Python::assume_attached();
562
+ let result: PyResult<Py<PyAny>> = (|| {
563
+ if data.is_null() {
564
+ return Err(PyValueError::new_err("input pointer for matrix repr is null"));
565
+ }
566
+ let mut quantity_data = [[Quantity::FloatQuantity(0.0); 3]; 3];
567
+ for i in 0..3 {
568
+ for j in 0..3 {
569
+ let repr = unsafe { *data.add(i * 3 + j) };
570
+ quantity_data[i][j] =
571
+ quantity_from_repr(repr).map_err(PyValueError::new_err)?;
572
+ }
573
+ }
574
+ let mat = Matrix3Py { data: quantity_data };
575
+ Ok(mat.into_pyobject(py)?.into_any().unbind())
576
+ })();
577
+ match result {
578
+ Ok(obj) => obj.into_ptr(),
579
+ Err(err) => {
580
+ err.restore(py);
581
+ std::ptr::null_mut()
582
+ }
583
+ }
584
+ }
585
+
586
+ pub fn register_unitforge_api(py: Python<'_>, m: &Bound<PyModule>) -> PyResult<()> {
587
+ let api = UnitforgeApiV1 {
588
+ version: UNITFORGE_API_VERSION,
589
+ extract_quantity_repr: extract_quantity_repr_impl,
590
+ quantity_repr_to_object: quantity_repr_to_object_impl,
591
+ extract_unit_repr: extract_unit_repr_impl,
592
+ unit_repr_to_object: unit_repr_to_object_impl,
593
+ extract_vector3_repr: extract_vector3_repr_impl,
594
+ vector3_repr_to_object: vector3_repr_to_object_impl,
595
+ extract_matrix3_repr: extract_matrix3_repr_impl,
596
+ matrix3_repr_to_object: matrix3_repr_to_object_impl,
597
+ };
598
+
599
+ let capsule = PyCapsule::new_with_destructor(
600
+ py,
601
+ api,
602
+ Some(CString::new(CAPSULE_NAME).unwrap()),
603
+ |_api, _ctx| {},
604
+ )?;
605
+ m.add(CAPSULE_NAME, capsule)?;
606
+ Ok(())
607
+ }
608
+
609
+ pub fn get_api<'py>(py: Python<'py>, module: &Bound<'py, PyModule>) -> PyResult<&'py UnitforgeApiV1> {
610
+ let capsule = module.getattr(CAPSULE_NAME)?.cast_into::<PyCapsule>()?;
611
+ if !capsule.is_valid() {
612
+ return Err(PyValueError::new_err(format!(
613
+ "Invalid capsule in unitforge module at attribute '{CAPSULE_NAME}'"
614
+ )));
615
+ }
616
+ let ptr = capsule.pointer();
617
+ if ptr.is_null() {
618
+ return Err(PyValueError::new_err(format!(
619
+ "Null pointer in unitforge capsule '{CAPSULE_NAME}'"
620
+ )));
621
+ }
622
+ unsafe { Ok(&*ptr.cast::<UnitforgeApiV1>()) }
623
+ }
624
+ }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "unitforge_macros"
3
- version = "0.3.11"
3
+ version = "0.3.13"
4
4
  edition = "2021"
5
5
  authors = ["Dr.Q team <info@askdrq.com>", "Henrik Stromberg <henrik@askdrq.com>"]
6
6
  description = "Proc macros for generating units, quantities, and Python bindings in unitforge"