From 8056d06cf88f0126f14691b9d651e2a71017095d Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Thu, 31 Jul 2025 13:44:14 -0500 Subject: [PATCH 1/2] Python support Add a new cargo feature, 'python', which enables pyo3 bindings on some structs. --- Cargo.lock | 94 +++++++++++++++++++++++++++++- modeling-cmds/Cargo.toml | 2 + modeling-cmds/src/format/dxf.rs | 1 + modeling-cmds/src/format/fbx.rs | 2 + modeling-cmds/src/format/gltf.rs | 2 + modeling-cmds/src/format/mod.rs | 1 + modeling-cmds/src/format/obj.rs | 2 + modeling-cmds/src/format/ply.rs | 2 + modeling-cmds/src/format/sldprt.rs | 1 + modeling-cmds/src/format/step.rs | 2 + modeling-cmds/src/format/stl.rs | 2 + 11 files changed, 109 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78621c63..be25520e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -510,7 +510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", - "target-lexicon", + "target-lexicon 0.12.14", ] [[package]] @@ -1848,6 +1848,12 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + [[package]] name = "inflections" version = "1.1.1" @@ -2059,6 +2065,7 @@ dependencies = [ "measurements", "parse-display 0.9.1", "parse-display-derive 0.9.1", + "pyo3", "schemars", "serde", "serde_bytes", @@ -2294,6 +2301,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -2376,7 +2392,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.7.1", "pin-utils", ] @@ -2938,6 +2954,68 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "pyo3" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" +dependencies = [ + "indoc", + "libc", + "memoffset 0.9.1", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" +dependencies = [ + "once_cell", + "target-lexicon 0.13.2", +] + +[[package]] +name = "pyo3-ffi" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.100", +] + [[package]] name = "qoi" version = "0.4.1" @@ -4105,6 +4183,12 @@ version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +[[package]] +name = "target-lexicon" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" + [[package]] name = "tempfile" version = "3.10.1" @@ -4587,6 +4671,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "universal-hash" version = "0.5.1" diff --git a/modeling-cmds/Cargo.toml b/modeling-cmds/Cargo.toml index 8a85c661..2a3adda0 100644 --- a/modeling-cmds/Cargo.toml +++ b/modeling-cmds/Cargo.toml @@ -23,6 +23,7 @@ convert_client_crate = ["dep:kittycad"] websocket = ["dep:serde_json"] webrtc = ["dep:webrtc"] unstable_exhaustive = [] +python = ["dep:pyo3"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -40,6 +41,7 @@ kittycad-unit-conversion-derive = "0.1.0" measurements = "0.11.0" parse-display = "0.9.1" parse-display-derive = "0.9.0" +pyo3 = { version = "0.25.1", optional = true } schemars = { version = "0.8.22", features = [ "bigdecimal04", "chrono", diff --git a/modeling-cmds/src/format/dxf.rs b/modeling-cmds/src/format/dxf.rs index b3b248aa..13c1facf 100644 --- a/modeling-cmds/src/format/dxf.rs +++ b/modeling-cmds/src/format/dxf.rs @@ -29,6 +29,7 @@ pub mod export { #[serde(rename = "DxfExportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] + #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options { /// Export storage. pub storage: Storage, diff --git a/modeling-cmds/src/format/fbx.rs b/modeling-cmds/src/format/fbx.rs index e185113e..fb3eace4 100644 --- a/modeling-cmds/src/format/fbx.rs +++ b/modeling-cmds/src/format/fbx.rs @@ -11,6 +11,7 @@ pub mod import { #[serde(rename = "FbxImportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] + #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options {} } @@ -22,6 +23,7 @@ pub mod export { #[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename = "FbxExportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] + #[cfg_attr(feature = "python", pyo3::pyclass)] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] pub struct Options { /// Specifies which kind of FBX will be exported. diff --git a/modeling-cmds/src/format/gltf.rs b/modeling-cmds/src/format/gltf.rs index 3f359afe..d8e9ab12 100644 --- a/modeling-cmds/src/format/gltf.rs +++ b/modeling-cmds/src/format/gltf.rs @@ -12,6 +12,7 @@ pub mod import { #[serde(rename = "GltfImportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] + #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options {} } @@ -23,6 +24,7 @@ pub mod export { #[display("storage: {storage}, presentation: {presentation}")] #[serde(rename = "GltfExportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] + #[cfg_attr(feature = "python", pyo3::pyclass)] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] pub struct Options { /// Specifies which kind of glTF 2.0 will be exported. diff --git a/modeling-cmds/src/format/mod.rs b/modeling-cmds/src/format/mod.rs index 7af6a268..173b8ef7 100644 --- a/modeling-cmds/src/format/mod.rs +++ b/modeling-cmds/src/format/mod.rs @@ -79,6 +79,7 @@ pub type InputFormat = InputFormat3d; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr)] #[serde(tag = "type", rename_all = "snake_case")] #[display(style = "snake_case")] +#[cfg_attr(feature = "python", pyo3::pyclass)] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] pub enum InputFormat3d { diff --git a/modeling-cmds/src/format/obj.rs b/modeling-cmds/src/format/obj.rs index 5bf5912f..94040adb 100644 --- a/modeling-cmds/src/format/obj.rs +++ b/modeling-cmds/src/format/obj.rs @@ -14,6 +14,7 @@ pub mod import { #[serde(rename = "ObjImportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] + #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options { /// Co-ordinate system of input data. /// @@ -50,6 +51,7 @@ pub mod export { #[display("coords: {coords}, units: {units}")] #[serde(rename = "ObjExportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] + #[cfg_attr(feature = "python", pyo3::pyclass)] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] pub struct Options { /// Co-ordinate system of output data. diff --git a/modeling-cmds/src/format/ply.rs b/modeling-cmds/src/format/ply.rs index 5862a2ce..f603b9bc 100644 --- a/modeling-cmds/src/format/ply.rs +++ b/modeling-cmds/src/format/ply.rs @@ -14,6 +14,7 @@ pub mod import { #[serde(rename = "PlyImportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] + #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options { /// Co-ordinate system of input data. /// @@ -51,6 +52,7 @@ pub mod export { #[display("coords: {coords}, selection: {selection}, storage: {storage}, units: {units}")] #[serde(rename = "PlyExportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] + #[cfg_attr(feature = "python", pyo3::pyclass)] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] pub struct Options { /// Co-ordinate system of output data. diff --git a/modeling-cmds/src/format/sldprt.rs b/modeling-cmds/src/format/sldprt.rs index 8c3f96ba..548a79cb 100644 --- a/modeling-cmds/src/format/sldprt.rs +++ b/modeling-cmds/src/format/sldprt.rs @@ -11,6 +11,7 @@ pub mod import { #[serde(default, rename = "SldprtImportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] + #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options { /// Splits all closed faces into two open faces. /// diff --git a/modeling-cmds/src/format/step.rs b/modeling-cmds/src/format/step.rs index 4ce831f2..057172c5 100644 --- a/modeling-cmds/src/format/step.rs +++ b/modeling-cmds/src/format/step.rs @@ -14,6 +14,7 @@ pub mod import { #[serde(default, rename = "StepImportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] + #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options { /// Splits all closed faces into two open faces. /// @@ -30,6 +31,7 @@ pub mod export { #[derive(Clone, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)] #[serde(rename = "StepExportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] + #[cfg_attr(feature = "python", pyo3::pyclass)] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] pub struct Options { /// Co-ordinate system of output data. diff --git a/modeling-cmds/src/format/stl.rs b/modeling-cmds/src/format/stl.rs index f42e7ce4..53f20597 100644 --- a/modeling-cmds/src/format/stl.rs +++ b/modeling-cmds/src/format/stl.rs @@ -14,6 +14,7 @@ pub mod import { #[serde(rename = "StlImportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] + #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options { /// Co-ordinate system of input data. /// @@ -50,6 +51,7 @@ pub mod export { #[display("coords: {coords}, selection: {selection}, storage: {storage}, units: {units}")] #[serde(rename = "StlExportOptions")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] + #[cfg_attr(feature = "python", pyo3::pyclass)] #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] pub struct Options { /// Co-ordinate system of output data. From 19ae88be19ee66dd76da7962c3c310ed92f5b4b3 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Thu, 31 Jul 2025 17:31:29 -0500 Subject: [PATCH 2/2] Add python constructors --- modeling-cmds/src/format/dxf.rs | 10 ++++++++++ modeling-cmds/src/format/fbx.rs | 20 ++++++++++++++++++++ modeling-cmds/src/format/gltf.rs | 20 ++++++++++++++++++++ modeling-cmds/src/format/obj.rs | 20 ++++++++++++++++++++ modeling-cmds/src/format/ply.rs | 20 ++++++++++++++++++++ modeling-cmds/src/format/sldprt.rs | 10 ++++++++++ modeling-cmds/src/format/step.rs | 20 ++++++++++++++++++++ modeling-cmds/src/format/stl.rs | 10 ++++++++++ 8 files changed, 130 insertions(+) diff --git a/modeling-cmds/src/format/dxf.rs b/modeling-cmds/src/format/dxf.rs index 13c1facf..e5febded 100644 --- a/modeling-cmds/src/format/dxf.rs +++ b/modeling-cmds/src/format/dxf.rs @@ -34,4 +34,14 @@ pub mod export { /// Export storage. pub storage: Storage, } + + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } } diff --git a/modeling-cmds/src/format/fbx.rs b/modeling-cmds/src/format/fbx.rs index fb3eace4..7d2609e6 100644 --- a/modeling-cmds/src/format/fbx.rs +++ b/modeling-cmds/src/format/fbx.rs @@ -13,6 +13,16 @@ pub mod import { #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options {} + + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } } /// Export models in FBX format. @@ -33,6 +43,16 @@ pub mod export { pub created: Option>, } + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } + impl std::fmt::Display for Options { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "storage: {}", self.storage) diff --git a/modeling-cmds/src/format/gltf.rs b/modeling-cmds/src/format/gltf.rs index d8e9ab12..b818c8d8 100644 --- a/modeling-cmds/src/format/gltf.rs +++ b/modeling-cmds/src/format/gltf.rs @@ -14,6 +14,16 @@ pub mod import { #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))] #[cfg_attr(feature = "python", pyo3::pyclass)] pub struct Options {} + + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } } /// Export models in KittyCAD's GLTF format. @@ -33,6 +43,16 @@ pub mod export { pub presentation: Presentation, } + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } + /// Describes the storage format of a glTF 2.0 scene. #[derive( Default, Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr, diff --git a/modeling-cmds/src/format/obj.rs b/modeling-cmds/src/format/obj.rs index 94040adb..db4249f4 100644 --- a/modeling-cmds/src/format/obj.rs +++ b/modeling-cmds/src/format/obj.rs @@ -32,6 +32,16 @@ pub mod import { pub units: UnitLength, } + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } + impl Default for Options { fn default() -> Self { Self { @@ -67,6 +77,16 @@ pub mod export { pub units: UnitLength, } + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } + impl Default for Options { fn default() -> Self { Self { diff --git a/modeling-cmds/src/format/ply.rs b/modeling-cmds/src/format/ply.rs index f603b9bc..cd5156b1 100644 --- a/modeling-cmds/src/format/ply.rs +++ b/modeling-cmds/src/format/ply.rs @@ -32,6 +32,16 @@ pub mod import { pub units: UnitLength, } + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } + impl Default for Options { fn default() -> Self { Self { @@ -74,6 +84,16 @@ pub mod export { pub units: UnitLength, } + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } + impl Default for Options { fn default() -> Self { Self { diff --git a/modeling-cmds/src/format/sldprt.rs b/modeling-cmds/src/format/sldprt.rs index 548a79cb..151380cb 100644 --- a/modeling-cmds/src/format/sldprt.rs +++ b/modeling-cmds/src/format/sldprt.rs @@ -18,4 +18,14 @@ pub mod import { /// Defaults to `false` but is implicitly `true` when importing into the engine. pub split_closed_faces: bool, } + + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } } diff --git a/modeling-cmds/src/format/step.rs b/modeling-cmds/src/format/step.rs index 057172c5..b0b64a61 100644 --- a/modeling-cmds/src/format/step.rs +++ b/modeling-cmds/src/format/step.rs @@ -21,6 +21,16 @@ pub mod import { /// Defaults to `false` but is implicitly `true` when importing into the engine. pub split_closed_faces: bool, } + + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } } /// Export models in STEP format. @@ -45,6 +55,16 @@ pub mod export { pub created: Option>, } + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } + impl Default for Options { fn default() -> Self { Self { diff --git a/modeling-cmds/src/format/stl.rs b/modeling-cmds/src/format/stl.rs index 53f20597..5c4da9b3 100644 --- a/modeling-cmds/src/format/stl.rs +++ b/modeling-cmds/src/format/stl.rs @@ -73,6 +73,16 @@ pub mod export { pub units: UnitLength, } + #[cfg(feature = "python")] + #[pyo3::pymethods] + impl Options { + #[new] + /// Set the options to their defaults. + pub fn new() -> Self { + Default::default() + } + } + impl Default for Options { fn default() -> Self { Self {