diff --git a/Cargo.lock b/Cargo.lock index 9bd3648bc..0cab29797 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=5a4c287236e03a23be4d5099a45b16346e9f9442#5a4c287236e03a23be4d5099a45b16346e9f9442" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=92578f0cc8e4eec0678b72c9bd1c701ff21b57b7#92578f0cc8e4eec0678b72c9bd1c701ff21b57b7" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index 1b2017efb..fecdf3307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ 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 = "5a4c287236e03a23be4d5099a45b16346e9f9442", features = ["acis", "offset"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "92578f0cc8e4eec0678b72c9bd1c701ff21b57b7", features = ["acis", "offset"] } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] } diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 4f8db1e15..328011fea 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -3945,9 +3945,20 @@ impl OpenCADStudio { .iter() .filter_map(|handle| self.tabs[i].scene.document.get_entity(*handle).cloned()) .collect(); - if let Some(solid) = sweep_model::lofted(&profiles) { + let result = sweep_model::loft_history(&profiles) + .and_then(|history| { + cadkernel::acis::rebuild_body(&history) + .ok() + .map(|solid| (solid, history)) + }) + .or_else(|| { + sweep_model::lofted(&profiles).map(|solid| { + let history = solid_history::brep_op(&solid); + (solid, history) + }) + }); + if let Some((solid, history)) = result { let pending = self.begin_undo(i, "LOFT", 1, true); - let history = solid_history::brep_op(&solid); let created = self.add_solid_model(empty_solid3d(), solid, history); if !created.is_null() { self.tabs[i].dirty = true; diff --git a/src/scene/model/solid_history.rs b/src/scene/model/solid_history.rs index ce8961844..a41358c00 100644 --- a/src/scene/model/solid_history.rs +++ b/src/scene/model/solid_history.rs @@ -1,7 +1,7 @@ use acadrust::entities::{EmbeddedEntity, Solid3D}; use acadrust::objects::{ DynamicBlockData, ObjectType, SolidHistoryBox, SolidHistoryBrep, SolidHistoryCone, - SolidHistoryCylinder, SolidHistoryNodeBase, SolidHistoryOperation, + SolidHistoryCylinder, SolidHistoryLoft, SolidHistoryNodeBase, SolidHistoryOperation, SolidHistoryPyramid, SolidHistorySphere, SolidHistorySweep, SolidHistoryTorus, }; use acadrust::EntityType; @@ -28,6 +28,7 @@ pub const GRIP_DRAFT: usize = 10_012; 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_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; @@ -2057,6 +2058,94 @@ fn apply_sweep_embedded_grip( true } +fn loft_section_grips(value: &SolidHistoryLoft) -> Vec { + if !value.guides.is_empty() { + return Vec::new(); + } + let Some(transform) = matrix(value.base.transform) else { + return Vec::new(); + }; + let mut result = Vec::new(); + let mut index = 0; + for section in &value.cross_sections { + let Some(entity) = embedded_entity(section) else { + continue; + }; + for source in entity.grips() { + let id = GRIP_LOFT_SECTION_FIRST + index; + index += 1; + let world = transform.transform_point3(source.world); + if !world.is_finite() { + continue; + } + let transform_vector = |vector: glam::DVec3| { + let vector = transform.transform_vector3(vector); + (vector.length_squared() > 1e-12).then(|| vector.normalize()) + }; + result.push(GripDef { + id, + world, + is_midpoint: source.is_midpoint, + shape: source.shape, + dir: source.dir.and_then(transform_vector), + axis: source.axis.and_then(transform_vector), + }); + } + } + result +} + +fn apply_loft_section_grip( + value: &mut SolidHistoryLoft, + mut index: usize, + apply: GripApply, +) -> bool { + if !value.guides.is_empty() { + return false; + } + let Some(transform) = matrix(value.base.transform) else { + return false; + }; + let inverse = transform.inverse(); + if !inverse.is_finite() { + return false; + } + let local_apply = match apply { + GripApply::Absolute(world) => { + let local = inverse.transform_point3(world); + if !local.is_finite() { + return false; + } + GripApply::Absolute(local) + } + GripApply::Translate(world_delta) => { + let local_delta = inverse.transform_vector3(world_delta); + if !local_delta.is_finite() || local_delta.length_squared() <= 1e-24 { + return false; + } + GripApply::Translate(local_delta) + } + }; + for section in &mut value.cross_sections { + let Some(mut entity) = embedded_entity(section) else { + continue; + }; + let grips = entity.grips(); + if index >= grips.len() { + index -= grips.len(); + continue; + } + let grip_id = grips[index].id; + entity.apply_grip(grip_id, local_apply); + let Some(entity) = into_embedded_entity(entity) else { + return false; + }; + *section = entity; + return true; + } + false +} + fn extrusion_draft_grip( value: &SolidHistorySweep, ) -> Option<(glam::DVec3, glam::DVec3, glam::DVec3, f64, f64)> { @@ -2382,6 +2471,7 @@ pub fn primitive_grips( GRIP_SWEEP_PATH_FIRST, )); } + SolidHistoryOperation::Loft(value) => grips.extend(loft_section_grips(value)), SolidHistoryOperation::Extrusion(value) => { let (profile_grips, profile_center) = extrusion_profile_grips(value); grips.extend(profile_grips); @@ -2418,6 +2508,11 @@ pub fn apply_primitive_grip( grip_id: usize, apply: GripApply, ) -> bool { + 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); + } + } if let SolidHistoryOperation::Sweep(value) = operation { if let Some(index) = grip_id.checked_sub(GRIP_SWEEP_PATH_FIRST) { return apply_sweep_embedded_grip( diff --git a/src/scene/model/sweep_model.rs b/src/scene/model/sweep_model.rs index cc2738e8a..f97bd0d79 100644 --- a/src/scene/model/sweep_model.rs +++ b/src/scene/model/sweep_model.rs @@ -5,7 +5,7 @@ use cadkernel::geom2d::{Arc, Curve, EllipseArc, Line}; use cadkernel::space::{PlanarCurve, Plane, Vec3}; use acadrust::entities::{EmbeddedEntity, LwPolyline, LwVertex, Spline}; use acadrust::objects::{ - SolidHistoryNodeBase, SolidHistoryOperation, SolidHistorySweep, + SolidHistoryLoft, SolidHistoryNodeBase, SolidHistoryOperation, SolidHistorySweep, }; use acadrust::types::{Vector2, Vector3}; use acadrust::EntityType; @@ -451,6 +451,26 @@ pub fn lofted(profiles: &[EntityType]) -> Option { brep::loft(§ions) } +/// Stores the source sections required to rebuild and edit a loft. +pub fn loft_history(profiles: &[EntityType]) -> Option { + let cross_sections = profiles + .iter() + .map(embedded_path) + .collect::>>()?; + if cross_sections.len() < 2 { + return None; + } + let mut base = SolidHistoryNodeBase::new(1); + base.transform = glam::DMat4::IDENTITY.to_cols_array(); + Some(SolidHistoryOperation::Loft(SolidHistoryLoft { + base, + operation_major: 1, + cross_sections, + guides: Vec::new(), + ..SolidHistoryLoft::default() + })) +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PolysolidJustification { Left,