diff --git a/Cargo.lock b/Cargo.lock index cb584ac0..5e3b08a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "acadrust" version = "0.4.1" -source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=7e2fa8c#7e2fa8c7c7c774edc4fa54b328b727dc76a3ad95" +source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=1ece83a#1ece83af08a93bf84cac16f919a4b274298c8c59" dependencies = [ "ahash 0.8.12", "anyhow", @@ -890,7 +890,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=2c73f76#2c73f76526c2e3c8032aa49dedddd5ac11876cd5" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=36c5b85#36c5b85919cd216c718c5b463fbea04da641bd17" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index 4627346a..d9083a51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,8 @@ glam = { version = "0.33", features = ["bytemuck"] } rfd = "0.17" clap = { version = "4", features = ["derive"] } env_logger = "0.11" -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7e2fa8c", features = ["serde"] } -cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "2c73f76", features = ["acis", "offset"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "1ece83a", features = ["serde"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "36c5b85", features = ["acis", "offset"] } acadifc = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "47b5603", optional = true } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" diff --git a/crates/ocs_plugin_api/Cargo.toml b/crates/ocs_plugin_api/Cargo.toml index 16eddf10..d91406bb 100644 --- a/crates/ocs_plugin_api/Cargo.toml +++ b/crates/ocs_plugin_api/Cargo.toml @@ -9,7 +9,7 @@ license = "GPL-3.0-only" # Pulled in only by the `host` feature, which adds the `acadrust`-typed # `HostApi` runtime surface. The default crate stays dependency-free so engine # crates and external tooling can depend on the manifest/ribbon contract cheaply. -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7e2fa8c", optional = true, features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "1ece83a", optional = true, features = ["serde"] } # Runtime IPC and serialization (host feature only). interprocess = { version = "2", optional = true } diff --git a/crates/ocs_web_worker/Cargo.toml b/crates/ocs_web_worker/Cargo.toml index a91fb837..0fbdefd9 100644 --- a/crates/ocs_web_worker/Cargo.toml +++ b/crates/ocs_web_worker/Cargo.toml @@ -8,7 +8,7 @@ publish = false crate-type = ["cdylib"] [dependencies] -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7e2fa8c", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "1ece83a", features = ["serde"] } bincode = "1.3" serde = { version = "1", features = ["derive"] } console_error_panic_hook = "0.1" diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index a9fb06a2..b78c5db6 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -1046,10 +1046,14 @@ impl OpenCADStudio { self.commit_undo_delta(i, pd); } } - CmdResult::CommitSolid { entity, solid } => { + CmdResult::CommitSolid { + entity, + solid, + history, + } => { let label = self.history_label_from_active_cmd(i, "SOLID"); let pending = self.begin_undo(i, label, 1, true); - self.add_solid_model(entity, *solid); + self.add_solid_model(entity, *solid, history); self.tabs[i].dirty = true; self.tabs[i].scene.clear_preview_wire(); self.tabs[i].active_cmd = None; @@ -2673,12 +2677,20 @@ impl OpenCADStudio { let result = sweep_model::extruded(&entity, height as f64) .and_then(|body| Some((solid_model::mesh_from_solid(&body, color)?, body))); if let Some((mesh, solid)) = result { + let history = crate::scene::model::solid_history::extrusion_op( + &entity, + height as f64, + ); let pending = self.begin_undo(i, "EXTRUDE", 1, true); let mut s3d = empty_solid3d(); if let acadrust::EntityType::Solid3D(inner) = &mut s3d { inner.wires = solid_model::edge_wires(&solid); } let new_handle = self.tabs[i].scene.add_entity(s3d); + self.tabs[i] + .scene + .document + .create_solid_history(new_handle, history); self.tabs[i].scene.register_solid_model(new_handle, solid); let _ = mesh; self.tabs[i].dirty = true; @@ -2726,12 +2738,22 @@ impl OpenCADStudio { ) .and_then(|body| Some((solid_model::mesh_from_solid(&body, color)?, body))); if let Some((mesh, solid)) = result { + let history = crate::scene::model::solid_history::revolve_op( + &entity, + axis_start.to_array(), + axis_end.to_array(), + (angle_deg as f64).to_radians(), + ); let pending = self.begin_undo(i, "REVOLVE", 1, true); let mut s3d = empty_solid3d(); if let acadrust::EntityType::Solid3D(inner) = &mut s3d { inner.wires = solid_model::edge_wires(&solid); } let new_handle = self.tabs[i].scene.add_entity(s3d); + self.tabs[i] + .scene + .document + .create_solid_history(new_handle, history); self.tabs[i].scene.register_solid_model(new_handle, solid); let _ = mesh; self.tabs[i].dirty = true; @@ -2768,6 +2790,12 @@ impl OpenCADStudio { .get_entity(profile_handle) .cloned(); let path_ent = self.tabs[i].scene.document.get_entity(path_handle).cloned(); + let history = profile_ent + .as_ref() + .zip(path_ent.as_ref()) + .map(|(profile, path)| { + crate::scene::model::solid_history::sweep_op(profile, path) + }); let result = profile_ent .zip(path_ent) .and_then(|(profile, path)| sweep_model::swept(&profile, &path, color)); @@ -2782,6 +2810,12 @@ impl OpenCADStudio { ); } let new_handle = self.tabs[i].scene.add_entity(entity); + if let Some(history) = history { + self.tabs[i] + .scene + .document + .create_solid_history(new_handle, history); + } for mesh in &mut set.lods { mesh.name = format!("{}", new_handle.value()); } @@ -2810,6 +2844,7 @@ impl OpenCADStudio { .filter_map(|handle| self.tabs[i].scene.document.get_entity(*handle).cloned()) .collect(); if let Some(mut set) = sweep_model::lofted(&profiles, color) { + let history = crate::scene::model::solid_history::loft_op(&profiles); let pending = self.begin_undo(i, "LOFT", 1, true); let mut entity = empty_solid3d(); if let acadrust::EntityType::Solid3D(solid) = &mut entity { @@ -2819,6 +2854,10 @@ impl OpenCADStudio { ); } let new_handle = self.tabs[i].scene.add_entity(entity); + self.tabs[i] + .scene + .document + .create_solid_history(new_handle, history); for mesh in &mut set.lods { mesh.name = format!("{}", new_handle.value()); } diff --git a/src/app/model_ops.rs b/src/app/model_ops.rs index c2846a95..90e2a6a6 100644 --- a/src/app/model_ops.rs +++ b/src/app/model_ops.rs @@ -7,23 +7,34 @@ // cylinder afterwards, and saves as one. use acadrust::entities::Solid3D; +use acadrust::objects::SolidHistoryOperation; use cadkernel::brep::Body; use acadrust::{EntityType, Handle}; use iced::Task; use super::Message; use crate::modules::model::boolean_cmd::BoolOp; +use crate::scene::model::solid_history; use crate::scene::model::solid_model::{self, Bool}; impl super::OpenCADStudio { /// Commit a Model-tab solid: add its acadrust entity to the document, then /// register the B-rep (caches it for booleans + tessellates it into the /// shaded mesh pipeline). Returns the new entity handle. - pub(super) fn add_solid_model(&mut self, entity: EntityType, solid: Body) -> Handle { + pub(super) fn add_solid_model( + &mut self, + entity: EntityType, + solid: Body, + history: SolidHistoryOperation, + ) -> Handle { let i = self.active_tab; let Some(handle) = self.commit_entity_handle(entity) else { return Handle::NULL; }; + self.tabs[i] + .scene + .document + .create_solid_history(handle, history); self.tabs[i].scene.register_solid_model(handle, solid); handle } @@ -66,7 +77,8 @@ impl super::OpenCADStudio { // mesh + cached B-rep; its edge wires make it pickable. let mut s3d = Solid3D::new(); s3d.wires = solid_model::edge_wires(&result); - let handle = self.add_solid_model(EntityType::Solid3D(s3d), result); + let history = solid_history::brep_op(&result); + let handle = self.add_solid_model(EntityType::Solid3D(s3d), result, history); self.tabs[i].scene.deselect_all(); if !handle.is_null() { self.tabs[i].scene.select_entity(handle, false); @@ -137,7 +149,8 @@ impl super::OpenCADStudio { self.tabs[i].scene.erase_entities(&handles); let mut s3d = Solid3D::new(); s3d.wires = solid_model::edge_wires(&result); - let handle = self.add_solid_model(EntityType::Solid3D(s3d), result); + let history = solid_history::brep_op(&result); + let handle = self.add_solid_model(EntityType::Solid3D(s3d), result, history); self.tabs[i].scene.deselect_all(); if !handle.is_null() { self.tabs[i].scene.select_entity(handle, false); @@ -176,7 +189,8 @@ impl super::OpenCADStudio { // Keep both originals; add the interference solid. let mut s3d = Solid3D::new(); s3d.wires = solid_model::edge_wires(&result); - self.add_solid_model(EntityType::Solid3D(s3d), result); + let history = solid_history::brep_op(&result); + self.add_solid_model(EntityType::Solid3D(s3d), result, history); self.tabs[i].dirty = true; self.refresh_properties(); self.command_line @@ -223,7 +237,8 @@ impl super::OpenCADStudio { self.tabs[i].scene.erase_entities(&handles); let mut s3d = Solid3D::new(); s3d.wires = solid_model::edge_wires(&rotated); - let handle = self.add_solid_model(EntityType::Solid3D(s3d), rotated); + let history = solid_history::brep_op(&rotated); + let handle = self.add_solid_model(EntityType::Solid3D(s3d), rotated, history); self.tabs[i].scene.deselect_all(); if !handle.is_null() { self.tabs[i].scene.select_entity(handle, false); @@ -306,7 +321,8 @@ impl super::OpenCADStudio { self.tabs[i].scene.erase_entities(&[handle]); let mut s3d = Solid3D::new(); s3d.wires = solid_model::edge_wires(&result); - let h = self.add_solid_model(EntityType::Solid3D(s3d), result); + let history = solid_history::brep_op(&result); + let h = self.add_solid_model(EntityType::Solid3D(s3d), result, history); self.tabs[i].scene.deselect_all(); if !h.is_null() { self.tabs[i].scene.select_entity(h, false); @@ -351,7 +367,8 @@ impl super::OpenCADStudio { self.push_undo_snapshot(i, "3DMIRROR"); let mut s3d = Solid3D::new(); s3d.wires = solid_model::edge_wires(&reflected); - let h = self.add_solid_model(EntityType::Solid3D(s3d), reflected); + let history = solid_history::brep_op(&reflected); + let h = self.add_solid_model(EntityType::Solid3D(s3d), reflected, history); self.tabs[i].scene.deselect_all(); if !h.is_null() { self.tabs[i].scene.select_entity(h, false); @@ -421,7 +438,8 @@ impl super::OpenCADStudio { self.tabs[i].scene.erase_entities(&handles); let mut s3d = Solid3D::new(); s3d.wires = solid_model::edge_wires(&aligned); - let h = self.add_solid_model(EntityType::Solid3D(s3d), aligned); + let history = solid_history::brep_op(&aligned); + let h = self.add_solid_model(EntityType::Solid3D(s3d), aligned, history); self.tabs[i].scene.deselect_all(); if !h.is_null() { self.tabs[i].scene.select_entity(h, false); @@ -514,7 +532,13 @@ impl super::OpenCADStudio { if let EntityType::Solid3D(inner) = &mut entity { inner.wires = solid_model::edge_wires(&solid); } - let handle = self.add_solid_model(entity, solid); + let history = solid_history::pyramid_op( + glam::DMat4::IDENTITY.to_cols_array(), + radius, + height, + n, + ); + let handle = self.add_solid_model(entity, solid, history); self.tabs[i].scene.deselect_all(); if !handle.is_null() { self.tabs[i].scene.select_entity(handle, false); diff --git a/src/command.rs b/src/command.rs index ad5d553d..04ff648c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1159,6 +1159,7 @@ pub enum CmdResult { CommitSolid { entity: EntityType, solid: Box, + history: acadrust::objects::SolidHistoryOperation, }, /// Commit an acadrust entity, end the command, and open the in-place text /// editor on it (used by MLEADER to type the annotation after placement). diff --git a/src/modules/model/primitive_cmd.rs b/src/modules/model/primitive_cmd.rs index 24d33cf5..1ad5b1ea 100644 --- a/src/modules/model/primitive_cmd.rs +++ b/src/modules/model/primitive_cmd.rs @@ -8,6 +8,7 @@ // entity is committed, so the Design-group boolean tools can combine it. use acadrust::entities::Solid3D; +use acadrust::objects::SolidHistoryOperation; use acadrust::{primitives, EntityType}; use glam::DVec3; use crate::t; @@ -119,10 +120,22 @@ impl PrimitiveCommand { preview } + fn history_transform(&self, origin: DVec3) -> [f64; 16] { + glam::DMat4::from_cols( + self.plane.x.extend(0.0), + self.plane.y.extend(0.0), + self.plane.z.extend(0.0), + self.plane.to_world(origin).extend(1.0), + ) + .to_cols_array() + } + /// Build both the acadrust `Solid3D` (ACIS, for persistence) and the /// kernel `Body` (rendering + booleans) from the footprint + `height`. - fn build(&self, height: f64) -> Option<(EntityType, Body)> { - let (doc, solid) = match self.shape { + fn build(&self, height: f64) -> Option<(EntityType, Body, SolidHistoryOperation)> { + use crate::scene::model::solid_history; + + let (doc, solid, history) = match self.shape { Shape::Box | Shape::Wedge => { let (a, b) = (self.pts[0], self.pts[1]); let length = (b.x - a.x).abs(); @@ -131,6 +144,7 @@ impl PrimitiveCommand { return None; } if self.shape == Shape::Box { + let origin = DVec3::new(a.x.min(b.x), a.y.min(b.y), a.z); let center = [ (a.x + b.x) / 2.0, (a.y + b.y) / 2.0, @@ -139,12 +153,24 @@ impl PrimitiveCommand { ( primitives::build_box(center, length, width, height), solid_model::box_solid(center, length, width, height), + solid_history::box_op( + self.history_transform(origin), + length, + width, + height, + ), ) } else { let origin = [a.x.min(b.x), a.y.min(b.y), a.z]; ( primitives::build_wedge(origin, length, width, height), solid_model::wedge_solid(origin, length, width, height), + solid_history::wedge_op( + self.history_transform(DVec3::from_array(origin)), + length, + width, + height, + ), ) } } @@ -159,11 +185,21 @@ impl PrimitiveCommand { ( primitives::build_cylinder(center, r, height), solid_model::cylinder_solid(center, r, height), + solid_history::cylinder_op( + self.history_transform(c), + r, + height, + ), ) } else { ( primitives::build_cone(center, r, height), solid_model::cone_solid(center, r, height), + solid_history::cone_op( + self.history_transform(c), + r, + height, + ), ) } } @@ -177,6 +213,7 @@ impl PrimitiveCommand { ( primitives::build_sphere(center, r), solid_model::sphere_solid(center, r), + solid_history::sphere_op(self.history_transform(c), r), ) } Shape::Torus => { @@ -190,6 +227,11 @@ impl PrimitiveCommand { ( primitives::build_torus(center, major, minor), solid_model::torus_solid(center, major, minor), + solid_history::torus_op( + self.history_transform(c), + major, + minor, + ), ) } }; @@ -199,12 +241,12 @@ impl PrimitiveCommand { // Edge wires make the solid click-pickable and draw a wireframe over // the shaded mesh. s3d.wires = solid_model::edge_wires(&solid); - Some((EntityType::Solid3D(s3d), solid)) + Some((EntityType::Solid3D(s3d), solid, history)) } fn commit(&self, height: f64) -> CmdResult { match self.build(height) { - Some((entity, solid)) => { + Some((entity, solid, history)) => { // Built upright in its own frame, then put on the working // plane — the same move `place_entity` makes for the ACIS // copy, so the two stay on top of each other. @@ -223,6 +265,7 @@ impl PrimitiveCommand { Some(placed) => CmdResult::CommitSolid { entity: self.plane.place_entity(entity), solid: Box::new(placed), + history, }, None => CmdResult::Cancel, } diff --git a/src/scene/model/mod.rs b/src/scene/model/mod.rs index a53e1e7e..3f5b8cbf 100644 --- a/src/scene/model/mod.rs +++ b/src/scene/model/mod.rs @@ -8,6 +8,7 @@ pub mod ole_pres; pub mod pdf_raster; pub mod mesh_model; pub mod solid_model; +pub mod solid_history; pub mod sweep_model; pub mod visual_style_model; pub mod object; diff --git a/src/scene/model/solid_history.rs b/src/scene/model/solid_history.rs new file mode 100644 index 00000000..2e7a1178 --- /dev/null +++ b/src/scene/model/solid_history.rs @@ -0,0 +1,202 @@ +use acadrust::entities::{EmbeddedEntity, Solid3D}; +use acadrust::objects::{ + SolidHistoryBox, SolidHistoryBrep, SolidHistoryCylinder, SolidHistoryLoft, + SolidHistoryNodeBase, SolidHistoryOperation, SolidHistoryPyramid, + SolidHistoryRevolve, SolidHistorySphere, SolidHistorySweep, SolidHistoryTorus, +}; +use acadrust::types::{Vector2, Vector3}; +use acadrust::EntityType; +use cadkernel::brep::Body; + +fn base(transform: [f64; 16]) -> SolidHistoryNodeBase { + let mut base = SolidHistoryNodeBase::new(1); + base.transform = transform; + base +} + +fn embedded(entity: &EntityType) -> Option { + Some(match entity { + EntityType::Point(value) => EmbeddedEntity::Point(value.clone()), + EntityType::Line(value) => EmbeddedEntity::Line(value.clone()), + EntityType::Arc(value) => EmbeddedEntity::Arc(value.clone()), + EntityType::Circle(value) => EmbeddedEntity::Circle(value.clone()), + EntityType::Ellipse(value) => EmbeddedEntity::Ellipse(value.clone()), + EntityType::Spline(value) => EmbeddedEntity::Spline(value.clone()), + EntityType::LwPolyline(value) => EmbeddedEntity::LwPolyline(value.clone()), + EntityType::Ray(value) => EmbeddedEntity::Ray(value.clone()), + EntityType::XLine(value) => EmbeddedEntity::XLine(value.clone()), + _ => return None, + }) +} + +pub fn box_op( + transform: [f64; 16], + length: f64, + width: f64, + height: f64, +) -> SolidHistoryOperation { + SolidHistoryOperation::Box(SolidHistoryBox { + base: base(transform), + operation_major: 1, + length, + width, + height, + ..SolidHistoryBox::default() + }) +} + +pub fn wedge_op( + transform: [f64; 16], + length: f64, + width: f64, + height: f64, +) -> SolidHistoryOperation { + SolidHistoryOperation::Wedge(SolidHistoryBox { + base: base(transform), + operation_major: 1, + length, + width, + height, + ..SolidHistoryBox::default() + }) +} + +pub fn cylinder_op( + transform: [f64; 16], + radius: f64, + height: f64, +) -> SolidHistoryOperation { + SolidHistoryOperation::Cylinder(SolidHistoryCylinder { + base: base(transform), + operation_major: 1, + height, + major_radius: radius, + minor_radius: radius, + x_radius: radius, + ..SolidHistoryCylinder::default() + }) +} + +pub fn cone_op( + transform: [f64; 16], + radius: f64, + height: f64, +) -> SolidHistoryOperation { + SolidHistoryOperation::Cone(SolidHistoryCylinder { + base: base(transform), + operation_major: 1, + height, + major_radius: radius, + minor_radius: radius, + x_radius: radius, + ..SolidHistoryCylinder::default() + }) +} + +pub fn sphere_op(transform: [f64; 16], radius: f64) -> SolidHistoryOperation { + SolidHistoryOperation::Sphere(SolidHistorySphere { + base: base(transform), + operation_major: 1, + radius, + ..SolidHistorySphere::default() + }) +} + +pub fn torus_op( + transform: [f64; 16], + major_radius: f64, + minor_radius: f64, +) -> SolidHistoryOperation { + SolidHistoryOperation::Torus(SolidHistoryTorus { + base: base(transform), + operation_major: 1, + major_radius, + minor_radius, + ..SolidHistoryTorus::default() + }) +} + +pub fn pyramid_op( + transform: [f64; 16], + radius: f64, + height: f64, + sides: usize, +) -> SolidHistoryOperation { + SolidHistoryOperation::Pyramid(SolidHistoryPyramid { + base: base(transform), + operation_major: 1, + height, + sides: sides as i32, + radius, + ..SolidHistoryPyramid::default() + }) +} + +pub fn brep_op(body: &Body) -> SolidHistoryOperation { + let acis_data = crate::scene::convert::acis_export::planar_solid_to_sat(body) + .map(|document| { + let mut solid = Solid3D::new(); + solid.set_sat_document(&document); + solid.acis_data + }) + .unwrap_or_default(); + SolidHistoryOperation::Brep(SolidHistoryBrep { + base: base(glam::DMat4::IDENTITY.to_cols_array()), + operation_major: 1, + acis_data, + ..SolidHistoryBrep::default() + }) +} + +pub fn extrusion_op(profile: &EntityType, height: f64) -> SolidHistoryOperation { + SolidHistoryOperation::Extrusion(SolidHistorySweep { + base: base(glam::DMat4::IDENTITY.to_cols_array()), + operation_major: 1, + direction: Vector3::new(0.0, 0.0, height), + sweep_entity: embedded(profile), + scale_factor: 1.0, + sweep_entity_transform: glam::DMat4::IDENTITY.to_cols_array(), + path_entity_transform: glam::DMat4::IDENTITY.to_cols_array(), + ..SolidHistorySweep::default() + }) +} + +pub fn sweep_op(profile: &EntityType, path: &EntityType) -> SolidHistoryOperation { + SolidHistoryOperation::Sweep(SolidHistorySweep { + base: base(glam::DMat4::IDENTITY.to_cols_array()), + operation_major: 1, + sweep_entity: embedded(profile), + path_entity: embedded(path), + scale_factor: 1.0, + sweep_entity_transform: glam::DMat4::IDENTITY.to_cols_array(), + path_entity_transform: glam::DMat4::IDENTITY.to_cols_array(), + ..SolidHistorySweep::default() + }) +} + +pub fn loft_op(profiles: &[EntityType]) -> SolidHistoryOperation { + SolidHistoryOperation::Loft(SolidHistoryLoft { + base: base(glam::DMat4::IDENTITY.to_cols_array()), + operation_major: 1, + cross_sections: profiles.iter().filter_map(embedded).collect(), + ..SolidHistoryLoft::default() + }) +} + +pub fn revolve_op( + profile: &EntityType, + axis_start: [f64; 3], + axis_end: [f64; 3], + angle: f64, +) -> SolidHistoryOperation { + let direction = glam::DVec3::from_array(axis_end) - glam::DVec3::from_array(axis_start); + SolidHistoryOperation::Revolve(SolidHistoryRevolve { + base: base(glam::DMat4::IDENTITY.to_cols_array()), + operation_major: 1, + axis_point: Vector3::new(axis_start[0], axis_start[1], axis_start[2]), + direction: Vector2::new(direction.x, direction.y), + revolve_angle: angle, + sweep_entity: embedded(profile), + ..SolidHistoryRevolve::default() + }) +}