{"id":25168528,"url":"https://github.com/nilaysavant/bevy_pmetra","last_synced_at":"2026-04-02T17:02:11.622Z","repository":{"id":238697275,"uuid":"762614508","full_name":"nilaysavant/bevy_pmetra","owner":"nilaysavant","description":"Parametric Modelling for Bevy using Truck CAD kernel.","archived":false,"fork":false,"pushed_at":"2026-03-25T22:41:18.000Z","size":4025,"stargazers_count":129,"open_issues_count":0,"forks_count":8,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-03-26T20:37:24.516Z","etag":null,"topics":["3d","3d-graphics","3dmodelling","bevy","bevy-engine","bevy-plugin","cad","computational-geometry","game-development","gamedev","parametric-modelling","procedural-generation"],"latest_commit_sha":null,"homepage":"https://pmetra.nilay.cc/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nilaysavant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-02-24T07:40:19.000Z","updated_at":"2026-03-25T01:28:57.000Z","dependencies_parsed_at":"2024-05-07T15:51:15.220Z","dependency_job_id":"31c71770-1b51-448f-8f10-c3527ba657ef","html_url":"https://github.com/nilaysavant/bevy_pmetra","commit_stats":null,"previous_names":["nilaysavant/bevy_pmetra"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/nilaysavant/bevy_pmetra","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilaysavant%2Fbevy_pmetra","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilaysavant%2Fbevy_pmetra/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilaysavant%2Fbevy_pmetra/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilaysavant%2Fbevy_pmetra/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nilaysavant","download_url":"https://codeload.github.com/nilaysavant/bevy_pmetra/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nilaysavant%2Fbevy_pmetra/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31311012,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["3d","3d-graphics","3dmodelling","bevy","bevy-engine","bevy-plugin","cad","computational-geometry","game-development","gamedev","parametric-modelling","procedural-generation"],"created_at":"2025-02-09T07:17:55.333Z","updated_at":"2026-04-02T17:02:11.616Z","avatar_url":"https://github.com/nilaysavant.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Bevy Pmetra\n\nParametric Modelling for [Bevy Engine][bevy-website] using [Truck][truck-github] CAD kernel.\n\nhttps://github.com/nilaysavant/bevy_pmetra/assets/47603308/4203fb15-539d-4ec6-b062-8519a9ec6c34\n\n[Web Demo][pmetra-demo-web]\n\n\u003c/div\u003e\n\n\u003e [!WARNING]\n\u003e Work in Progress! Feel free to try it out, fork and mod it for your use case. You are welcome to contribute your changes upstream. I will be glad to review and merge them if they fit the vision/scope of the project. Would love any feedback and help!\n\n## Add Crate\n\nAdd the dependency in your project's `Cargo.toml`. Make sure you're using the right version _tag_. Refer to the [Bevy Compatibility](#bevy-compatibility) table.\n\n```toml\n[dependencies]\nbevy_pmetra = { git = \"https://github.com/nilaysavant/bevy_pmetra\", tag = \"v0.7.0\" }\n```\n\n## Create Simple Parametric Cube\n\nRefer to [`examples/simple_cube.rs`](https://github.com/nilaysavant/bevy_pmetra/blob/master/examples/simple_cube.rs) for full example.\n\n### Create struct\n\nCreate _struct_ for the `SimpleCube` with **fields** being the **parameters**:\n\n```rs\n#[derive(Debug, Reflect, Component, Clone)]\nstruct SimpleCube {\n    /// Length of the side of the cube.\n    side_length: f64,\n    /// Number of cubes to spawn.\n    array_count: u32,\n}\n```\n\n- Make sure to derive `Component`. As it will be added to the _root_/_parent_ `SpatialBundle` entity of the parametric model. Also derive `Clone`.\n- Optionally derive `Reflect` and `Debug`.\n\nImplement `Default` for default values of parameters:\n\n```rs\nimpl Default for SimpleCube {\n  fn default() -\u003e Self {\n      Self {\n          side_length: 0.5,\n          array_count: 1,\n      }\n  }\n}\n```\n\n### Implement Traits\n\nImplement a few `traits` on our `SimpleCube` _struct_ for _parametric_ behavior:\n\n- [`PmetraCad`](#pmetracad): For generating multiple `CadShell`(s) using this struct via Truck's modelling APIs. `CadShell` is a wrapper around Truck's [`Shell`](https://docs.rs/truck-topology/0.1.1/truck_topology/struct.Shell.html).\n- [`PmetraModelling`](#pmetramodelling): For parametrically generating `Mesh`(s) from `CadShell`(s).\n- [`PmetraInteractions`](#pmetrainteractions): (Optional) Setup interactions for live manipulations on models using `CadSlider`(s).\n\n#### PmetraCad\n\n```rs\nimpl PmetraCad for SimpleCube {\n    fn shells_builders(\u0026self) -\u003e Result\u003cCadShellsBuilders\u003cSelf\u003e\u003e {\n        CadShellsBuilders::new(self.clone())?\n            .add_shell_builder(CadShellName(\"SimpleCube\".into()), cube_shell_builder)\n    }\n}\n```\n\n- `CadShellsBuilders` lets us add multiple `CadShell` builders per parametric model. Each builder is added as a callback function.\n- Since we only have a single kind of geometry/mesh we just need to add one shell builder for our cube: `cube_shell_builder`. This will need to return a `CadShell` for our cube. We give it a name `\"SimpleCube\"` which we can reference later.\n- If we need another kind of geometry/mesh (eg. cylinder, rectangle etc) we can add more such builders which will generate their equivalent shells. NB: We can only use the parameters of our `SimpleCube` _struct_ to generate all the `CadShell`(s).\n\nHere is the code for `cube_shell_builder`:\n\n```rs\nfn cube_shell_builder(params: \u0026SimpleCube) -\u003e Result\u003cCadShell\u003e {\n    let SimpleCube { side_length, .. } = \u0026params;\n    let mut tagged_elements = CadTaggedElements::default();\n    let vertex = Vertex::new(Point3::new(-side_length / 2., 0., side_length / 2.));\n    let edge = builder::tsweep(\u0026vertex, Vector3::unit_x() * *side_length);\n    let face = builder::tsweep(\u0026edge, -Vector3::unit_z() * *side_length);\n    tagged_elements.insert(\n        CadElementTag(\"ProfileFace\".into()),\n        CadElement::Face(face.clone()),\n    );\n    let solid = builder::tsweep(\u0026face, Vector3::unit_y() * *side_length);\n    let shell = Shell::try_from_solid(\u0026solid)?;\n    Ok(CadShell {\n        shell,\n        tagged_elements,\n    })\n}\n```\n\n- We model the cube using Truck's APIs. Refer [Truck Cube Modelling Tutorial](https://ricos.gitlab.io/truck-tutorial/v0.1/modeling.html#cube).\n- We additionally _tag_ the `\"ProfileFace\"`. This helps with positioning/orienting things like `CadSliders` with respect to the tagged element, as we will see later.\n- We can tag Truck's primitives like `Vertex`, `Edge`, `Wire`, `Face` etc.\n\n#### PmetraModelling\n\n```rs\nimpl PmetraModelling for SimpleCube {\n    fn meshes_builders_by_shell(\n        \u0026self,\n        shells_by_name: \u0026CadShellsByName,\n    ) -\u003e Result\u003cCadMeshesBuildersByCadShell\u003cSelf\u003e\u003e {\n        let mut meshes_builders_by_shell =\n            CadMeshesBuildersByCadShell::new(self.clone(), shells_by_name.clone())?;\n        let shell_name = CadShellName(\"SimpleCube\".into());\n        for i in 0..self.array_count {\n            meshes_builders_by_shell.add_mesh_builder_with_outlines(\n                shell_name.clone(),\n                \"SimpleCube\".to_string() + \u0026i.to_string(),\n                CadMeshBuilder::new(self.clone(), shell_name.clone())? // builder\n                    .set_transform(Transform::from_translation(\n                        Vec3::X * (i as f32 * (self.side_length as f32 * 1.5)),\n                    ))?\n                    .set_base_material(Color::RED.into())?,\n            )?;\n        }\n        Ok(meshes_builders_by_shell)\n    }\n}\n```\n\n- `CadShellsByName` holds the `CadShell`(s) by the given name. In our case we will have a shell for `\"SimpleCube\"` name.\n- `CadMeshesBuildersByCadShell` is used generate multiple `Mesh`(s) for each defined `CadShell` via a `CadMeshBuilder`.\n- We add a new mesh builder for our cube using `add_mesh_builder_with_outlines()`, which includes adding outlines for the generated meshes. We can use `add_mesh_builder()` for no outlines (**more performance**!).\n- To the above we pass the `shell_name`, a name for the mesh we will be generating, along with the builder for the same.\n- The `CadMeshBuilder` takes the parameter struct and the `shell_name`. We can set the `Transform` and the `Material` of our mesh here.\n- Since we want to _array_ the cubes (using `array_count`), we run this inside a for loop passing down the index (for naming) and also set the **transform** for each cube.\n\n\u003e [!TIP]\n\u003e If you do not need _interactions_ you can skip the [PmetraInteractions](#pmetrainteractions) section and jump to the [Plugins](#pmetra-plugins) section. With this you can already have parametric behavior. Just query for your parametric struct as a component (eg. `SimpleCube`), and adjust its parameters!\n\n#### PmetraInteractions\n\nOptionally you can implement this `trait` for _interactive_ _sliders_ that can manipulate parameters of the `SimpleCube`:\n\n```rs\nimpl PmetraInteractions for SimpleCube {\n    fn sliders(\u0026self, shells_by_name: \u0026CadShellsByName) -\u003e Result\u003cCadSliders\u003e {\n        let sliders = CadSliders::default() // sliders\n            .add_slider(\n                CadSliderName(\"SideLengthSlider\".into()),\n                build_side_length_slider(self, shells_by_name)?,\n            )?\n            .add_slider(\n                CadSliderName(\"ArrayCountSlider\".into()),\n                build_array_count_slider(self, shells_by_name)?,\n            )?;\n        Ok(sliders)\n    }\n\n    fn on_slider_transform(\n        \u0026mut self,\n        name: CadSliderName,\n        prev_transform: Transform,\n        new_transform: Transform,\n    ) {\n        if name.0 == \"SideLengthSlider\" {\n            let delta = new_transform.translation - prev_transform.translation;\n            if delta.length() \u003e 0. {\n                self.side_length += delta.z as f64;\n            }\n        } else if name.0 == \"ArrayCountSlider\" {\n            let delta = new_transform.translation - prev_transform.translation;\n            if delta.length() \u003e 0. {\n                self.array_count = (new_transform.translation.x / (self.side_length as f32 * 1.5))\n                    .floor() as u32\n                    + 1;\n            }\n        }\n    }\n\n    fn on_slider_tooltip(\u0026self, name: CadSliderName) -\u003e Result\u003cOption\u003cString\u003e\u003e {\n        if name.0 == \"SideLengthSlider\" {\n            Ok(Some(format!(\"side_length: {:.2}\", self.side_length)))\n        } else if name.0 == \"ArrayCountSlider\" {\n            Ok(Some(format!(\"array_count: {}\", self.array_count)))\n        } else {\n            Ok(None)\n        }\n    }\n}\n```\n\n- We configure the sliders using the `CadSliders` builder in `sliders()` using the received `shells_by_name`.\n- Each slider can be added using `add_slider()` which accepts the name of the slider (for future reference/ID) and the `CadSlider` struct itself.\n- Here we use utility functions to create the `CadSlider` struct like `build_side_length_slider` for `\"SideLengthSlider\"`.\n- `on_slider_transform` is called by the plugin whenever a slider's _transform_ is changed. We receive the `prev_transform` and the `new_transform` using which can change the parameters of our `SimpleCube` struct. The name of the slider is useful to distinguish and apply changes from the correct slider.\n- `on_slider_tooltip` is used to (optionally) set the tooltip text for the _active_ slider.\n\nHere is the code for `build_side_length_slider`:\n\n```rs\nfn build_side_length_slider(\n    params: \u0026SimpleCube,\n    shells_by_name: \u0026CadShellsByName,\n) -\u003e Result\u003cCadSlider\u003e {\n    let SimpleCube { side_length, .. } = \u0026params;\n    let cad_shell = shells_by_name\n        .get(\u0026CadShellName(\"SimpleCube\".to_string()))\n        .ok_or_else(|| anyhow!(\"Could not get cube shell!\"))?;\n    let Some(CadElement::Face(face)) =\n        cad_shell.get_element_by_tag(CadElementTag::new(\"ProfileFace\"))\n    else {\n        return Err(anyhow!(\"Could not find face!\"));\n    };\n    let face_normal = face.oriented_surface().normal(0.5, 0.5).as_bevy_vec3();\n    let face_boundaries = face.boundaries();\n    let face_wire = face_boundaries.last().expect(\"No wire found!\");\n    let face_centroid = face_wire.get_centroid();\n    let slider_pos = face_centroid.as_vec3() + Vec3::Z * (*side_length as f32 / 2. + 0.1);\n    let slider_transform = Transform::from_translation(slider_pos)\n        .with_rotation(get_rotation_from_normals(Vec3::Z, face_normal));\n    Ok(CadSlider {\n        drag_plane_normal: face_normal,\n        transform: slider_transform,\n        slider_type: CadSliderType::Linear {\n            direction: Vec3::Z,\n            limit_min: Some(Vec3::Z * 0.2),\n            limit_max: Some(Vec3::INFINITY),\n        },\n        ..default()\n    })\n}\n```\n\n- We used the `\"ProfileFace\"` tag (we added earlier) to calculate the slider's `Transform` and also set the normal of the _drag plane_.\n- 2 types of sliders supported: `Linear` and `Planer`. `Linear` also allows setting the drag _limits_ of the slider along the given _direction_.\n\n### Pmetra Plugins\n\nNow you can add the Pmetra Plugins to your Bevy App:\n\n```rs\nApp::new() // app\n    .add_plugins((\n        PmetraBasePlugin::default(), // Base plugin\n        PmetraModellingPlugin::\u003cSimpleCube\u003e::default(),\n        PmetraInteractionsPlugin::\u003cSimpleCube\u003e::default(), // Optional\n    ))\n```\n\n- `PmetraBasePlugin` is required and needs to be added only once per app.\n- `PmetraModellingPlugin` is required to be added for each parametric `struct`. `SimpleCube` in this case.\n- `PmetraInteractionsPlugin` can be optionally added for the _interactive sliders_.\n\n### Generate Model\n\nNow you can spawn the `SimpleCube` model by firing an `Event`:\n\n```rs\nfn spawn_simple_cube_model(mut spawn_simple_cube: EventWriter\u003cGenerateCadModel\u003cSimpleCube\u003e\u003e) {\n    spawn_simple_cube.send(GenerateCadModel::default());\n}\n```\n\nThats it! You can now see the magic:\n\n![simple_cube_example_1](https://github.com/nilaysavant/bevy_pmetra/assets/47603308/babf4647-ab9b-4da4-a1f2-0254b0e30bf3)\n\n\u003e [!TIP]\n\u003e For more sophisticated examples, checkout the models in the demo:\n\u003e [`pmetra_demo/src/utils/cad_models`](https://github.com/nilaysavant/bevy_pmetra/tree/master/pmetra_demo/src/utils/cad_models)\n\n## Bevy Compatibility\n\n| bevy | bevy_pmetra        |\n| ---- | ------------------ |\n| 0.18 | `master`, `v0.7.0` |\n| 0.17 | `v0.6.0`           |\n| 0.16 | `v0.4.0`, `v0.5.0` |\n| 0.15 | `v0.3.0`           |\n| 0.14 | `v0.2.x`           |\n| 0.13 | `v0.1.0`           |\n\n[bevy-website]: https://bevyengine.org/\n[truck-github]: https://github.com/ricosjp/truck\n[pmetra-demo-web]: https://pmetra.nilay.cc/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnilaysavant%2Fbevy_pmetra","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnilaysavant%2Fbevy_pmetra","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnilaysavant%2Fbevy_pmetra/lists"}