diff --git a/Cargo.lock b/Cargo.lock index 0cab29797..0e79115dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,8 +71,8 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" -version = "0.4.1" -source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=a0f7d444f1607bc4b2c881060cbe7ea1014253cb#a0f7d444f1607bc4b2c881060cbe7ea1014253cb" +version = "0.5.1" +source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=393d6c0857447f9ca678bed97a612707f0a842c6#393d6c0857447f9ca678bed97a612707f0a842c6" dependencies = [ "ahash 0.8.12", "anyhow", @@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=92578f0cc8e4eec0678b72c9bd1c701ff21b57b7#92578f0cc8e4eec0678b72c9bd1c701ff21b57b7" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=fb5a5c18f794df30e97c712fb5d4fe7c6629d1d5#fb5a5c18f794df30e97c712fb5d4fe7c6629d1d5" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index fecdf3307..e7dbf0d58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,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 = "a0f7d444f1607bc4b2c881060cbe7ea1014253cb", features = ["serde"] } -cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "92578f0cc8e4eec0678b72c9bd1c701ff21b57b7", features = ["acis", "offset"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "393d6c0857447f9ca678bed97a612707f0a842c6", features = ["serde"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "fb5a5c18f794df30e97c712fb5d4fe7c6629d1d5", features = ["acis", "offset"] } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] } @@ -61,7 +61,7 @@ iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aa iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } [patch."https://github.com/HakanSeven12/cadcodec.git"] -acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "a0f7d444f1607bc4b2c881060cbe7ea1014253cb" } +acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "393d6c0857447f9ca678bed97a612707f0a842c6" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 328011fea..4083ab14b 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -3837,18 +3837,27 @@ impl OpenCADStudio { let entity_opt = self.tabs[i].scene.document.get_entity(handle).cloned(); if let Some(entity) = entity_opt { - let result = sweep_model::revolved( - &entity, - [ - axis_start.x as f64, - axis_start.y as f64, - axis_start.z as f64, - ], - [axis_end.x as f64, axis_end.y as f64, axis_end.z as f64], - (angle_deg as f64).to_radians(), - ); - if let Some(solid) = result { - let history = crate::scene::model::solid_history::brep_op(&solid); + let from = [ + axis_start.x as f64, + axis_start.y as f64, + axis_start.z as f64, + ]; + let to = [axis_end.x as f64, axis_end.y as f64, axis_end.z as f64]; + let angle = (angle_deg as f64).to_radians(); + let result = sweep_model::revolve_history(&entity, from, to, angle) + .and_then(|history| { + cadkernel::acis::rebuild_body(&history) + .ok() + .map(|solid| (solid, history)) + }) + .or_else(|| { + sweep_model::revolved(&entity, from, to, angle).map(|solid| { + let history = + crate::scene::model::solid_history::brep_op(&solid); + (solid, history) + }) + }); + if let Some((solid, history)) = result { let pending = self.begin_undo(i, "REVOLVE", 1, true); let created = self.add_solid_model(empty_solid3d(), solid, history); if !created.is_null() { diff --git a/src/scene/model/solid_history.rs b/src/scene/model/solid_history.rs index a41358c00..c32d63dd1 100644 --- a/src/scene/model/solid_history.rs +++ b/src/scene/model/solid_history.rs @@ -2,7 +2,8 @@ use acadrust::entities::{EmbeddedEntity, Solid3D}; use acadrust::objects::{ DynamicBlockData, ObjectType, SolidHistoryBox, SolidHistoryBrep, SolidHistoryCone, SolidHistoryCylinder, SolidHistoryLoft, SolidHistoryNodeBase, SolidHistoryOperation, - SolidHistoryPyramid, SolidHistorySphere, SolidHistorySweep, SolidHistoryTorus, + SolidHistoryPyramid, SolidHistoryRevolve, SolidHistorySphere, SolidHistorySweep, + SolidHistoryTorus, }; use acadrust::EntityType; use cadkernel::brep::Body; @@ -29,6 +30,8 @@ pub const GRIP_PROFILE_FIRST: usize = 10_200; pub const GRIP_SWEEP_PROFILE_FIRST: usize = 20_000; pub const GRIP_SWEEP_PATH_FIRST: usize = 30_000; pub const GRIP_LOFT_SECTION_FIRST: usize = 40_000; +pub const GRIP_REVOLVE_PROFILE_FIRST: usize = 50_000; +pub const GRIP_REVOLVE_AXIS: usize = 10_013; pub const GRIP_BOX_CORNER_FIRST: usize = 10_100; pub const GRIP_BOX_FACE_X_MIN: usize = 10_110; pub const GRIP_BOX_FACE_X_MAX: usize = 10_111; @@ -2146,6 +2149,100 @@ fn apply_loft_section_grip( false } +fn revolve_profile_matrix(value: &SolidHistoryRevolve) -> Option { + let base = matrix(value.base.transform)?; + let axis_point = glam::DVec3::new( + value.axis_point.x, + value.axis_point.y, + value.axis_point.z, + ); + let axis = glam::DVec3::new(value.direction.x, value.direction.y, value.direction.z) + .try_normalize()?; + if !axis_point.is_finite() || !value.start_angle.is_finite() { + return None; + } + Some( + base + * glam::DMat4::from_translation(axis_point) + * glam::DMat4::from_axis_angle(axis, value.start_angle) + * glam::DMat4::from_translation(-axis_point), + ) +} + +fn revolve_profile_grips(value: &SolidHistoryRevolve) -> Vec { + let Some(profile) = value.sweep_entity.as_ref().and_then(embedded_entity) else { + return Vec::new(); + }; + let Some(transform) = revolve_profile_matrix(value) else { + return Vec::new(); + }; + profile + .grips() + .into_iter() + .enumerate() + .filter_map(|(index, source)| { + let world = transform.transform_point3(source.world); + if !world.is_finite() { + return None; + } + let transform_vector = |vector: glam::DVec3| { + let vector = transform.transform_vector3(vector); + (vector.length_squared() > 1e-12).then(|| vector.normalize()) + }; + Some(GripDef { + id: GRIP_REVOLVE_PROFILE_FIRST + index, + world, + is_midpoint: source.is_midpoint, + shape: source.shape, + dir: source.dir.and_then(transform_vector), + axis: source.axis.and_then(transform_vector), + }) + }) + .collect() +} + +fn apply_revolve_profile_grip( + value: &mut SolidHistoryRevolve, + index: usize, + apply: GripApply, +) -> bool { + let Some(mut profile) = value.sweep_entity.as_ref().and_then(embedded_entity) else { + return false; + }; + let Some(source) = profile.grips().get(index).cloned() else { + return false; + }; + let Some(transform) = revolve_profile_matrix(value) else { + return false; + }; + let inverse = transform.inverse(); + if !inverse.is_finite() { + return false; + } + let apply = match apply { + GripApply::Absolute(world) => { + let local = inverse.transform_point3(world); + if !local.is_finite() { + return false; + } + GripApply::Absolute(local) + } + GripApply::Translate(delta) => { + let local = inverse.transform_vector3(delta); + if !local.is_finite() || local.length_squared() <= 1e-24 { + return false; + } + GripApply::Translate(local) + } + }; + profile.apply_grip(source.id, apply); + let Some(profile) = into_embedded_entity(profile) else { + return false; + }; + value.sweep_entity = Some(profile); + true +} + fn extrusion_draft_grip( value: &SolidHistorySweep, ) -> Option<(glam::DVec3, glam::DVec3, glam::DVec3, f64, f64)> { @@ -2472,6 +2569,16 @@ pub fn primitive_grips( )); } SolidHistoryOperation::Loft(value) => grips.extend(loft_section_grips(value)), + SolidHistoryOperation::Revolve(value) => { + add( + GRIP_REVOLVE_AXIS, + value.base.transform, + [value.axis_point.x, value.axis_point.y, value.axis_point.z], + GripShape::Square, + None, + ); + grips.extend(revolve_profile_grips(value)); + } SolidHistoryOperation::Extrusion(value) => { let (profile_grips, profile_center) = extrusion_profile_grips(value); grips.extend(profile_grips); @@ -2508,6 +2615,11 @@ pub fn apply_primitive_grip( grip_id: usize, apply: GripApply, ) -> bool { + if let SolidHistoryOperation::Revolve(value) = operation { + if let Some(index) = grip_id.checked_sub(GRIP_REVOLVE_PROFILE_FIRST) { + return apply_revolve_profile_grip(value, index, apply); + } + } if let SolidHistoryOperation::Loft(value) = operation { if let Some(index) = grip_id.checked_sub(GRIP_LOFT_SECTION_FIRST) { return apply_loft_section_grip(value, index, apply); @@ -2677,6 +2789,9 @@ pub fn apply_primitive_grip( } _ => return false, }, + SolidHistoryOperation::Revolve(value) if grip_id == GRIP_REVOLVE_AXIS => { + value.axis_point = acadrust::types::Vector3::new(local.x, local.y, local.z); + } SolidHistoryOperation::Extrusion(value) => match grip_id { GRIP_HEIGHT => { if value.path_entity.is_some() { diff --git a/src/scene/model/sweep_model.rs b/src/scene/model/sweep_model.rs index f97bd0d79..f7ff2456d 100644 --- a/src/scene/model/sweep_model.rs +++ b/src/scene/model/sweep_model.rs @@ -396,6 +396,45 @@ pub fn revolved( ) } +fn profile_center(profile: &Profile) -> Option { + let count = profile.pieces.len(); + let sum = profile.pieces.iter().try_fold(Vec3::ZERO, |sum, piece| { + let point = Vec3::from(profile.plane.point_at(piece.point_at(0.0))); + point.is_finite().then_some(sum + point) + })?; + (count > 0).then_some(sum / count as f64) +} + +/// Stores the source profile and canonical axis required to rebuild and edit +/// a revolved solid. +pub fn revolve_history( + entity: &EntityType, + from: [f64; 3], + to: [f64; 3], + angle: f64, +) -> Option { + if !angle.is_finite() || angle.abs() <= 1e-12 { + return None; + } + let profile = profile_of(entity)?; + let from = Vec3::from(from); + let direction = (Vec3::from(to) - from).normalize()?; + let center = profile_center(&profile)?; + let axis_point = from + direction * (center - from).dot(direction); + let mut base = SolidHistoryNodeBase::new(1); + base.transform = glam::DMat4::IDENTITY.to_cols_array(); + Some(SolidHistoryOperation::Revolve(SolidHistoryRevolve { + base, + operation_major: 1, + axis_point: Vector3::new(axis_point.x, axis_point.y, axis_point.z), + direction: Vector3::new(direction.x, direction.y, direction.z), + revolve_angle: angle, + flag_290: true, + sweep_entity: Some(embedded_path(entity)?), + ..SolidHistoryRevolve::default() + })) +} + /// SWEEP as an exact B-rep along straight and circular path pieces. pub fn swept(profile: &EntityType, path: &EntityType) -> Option { let profile = profile_of(profile)?;