From 15c44c0f3a3d8c0ab883255b7d3f32a21e6161f3 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:01:35 +0300 Subject: [PATCH] Restore revolve profile and axis grips --- Cargo.lock | 6 +- Cargo.toml | 6 +- src/app/command_driver.rs | 33 ++++--- src/scene/model/solid_history.rs | 151 ++++++++++++++++++++++++++++++- src/scene/model/sweep_model.rs | 54 ++++++++++- 5 files changed, 229 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df993e5a8..78e48ab4c 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=ad9b1a25ec0d3dc217e24d9c525709ec18484603#ad9b1a25ec0d3dc217e24d9c525709ec18484603" 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=a809cb32cc6d90fd0e9c4199c3b4977cd56b7fa8#a809cb32cc6d90fd0e9c4199c3b4977cd56b7fa8" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=76ae7f836f5e6d7aa81cefb79330698e38adf95a#76ae7f836f5e6d7aa81cefb79330698e38adf95a" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index 6a5e39342..dc8114115 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 = "a809cb32cc6d90fd0e9c4199c3b4977cd56b7fa8", features = ["acis", "offset"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "ad9b1a25ec0d3dc217e24d9c525709ec18484603", features = ["serde"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "76ae7f836f5e6d7aa81cefb79330698e38adf95a", 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 = "ad9b1a25ec0d3dc217e24d9c525709ec18484603" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 150fd1381..f7636aebd 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -3566,18 +3566,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 3542bf6dd..68b23d0f8 100644 --- a/src/scene/model/solid_history.rs +++ b/src/scene/model/solid_history.rs @@ -1,12 +1,15 @@ -use acadrust::entities::Solid3D; +use acadrust::entities::{EmbeddedEntity, Solid3D}; use acadrust::objects::{ DynamicBlockData, ObjectType, SolidHistoryBox, SolidHistoryBrep, SolidHistoryCone, SolidHistoryCylinder, SolidHistoryNodeBase, SolidHistoryOperation, - SolidHistoryPyramid, SolidHistorySphere, SolidHistorySweep, SolidHistoryTorus, + SolidHistoryPyramid, SolidHistoryRevolve, SolidHistorySphere, SolidHistorySweep, + SolidHistoryTorus, }; +use acadrust::EntityType; use cadkernel::brep::Body; use crate::command::EntityTransform; +use crate::entities::traits::EntityTypeOps; use crate::scene::model::object::{ GripApply, GripDef, GripShape, PropSection, PropValue, Property, }; @@ -23,6 +26,8 @@ pub const GRIP_MAJOR_RADIUS: usize = 10_008; pub const GRIP_MINOR_RADIUS: usize = 10_009; pub const GRIP_TOP_RADIUS: usize = 10_010; pub const GRIP_POSITION: usize = 10_011; +pub const GRIP_REVOLVE_AXIS: usize = 10_012; +pub const GRIP_REVOLVE_PROFILE_FIRST: usize = 50_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; @@ -1641,6 +1646,130 @@ fn local_point(transform: [f64; 16], point: glam::DVec3) -> Option Some(matrix(transform)?.inverse().transform_point3(point)) } +fn embedded_entity(value: &EmbeddedEntity) -> Option { + Some(match value { + EmbeddedEntity::Point(value) => EntityType::Point(value.clone()), + EmbeddedEntity::Line(value) => EntityType::Line(value.clone()), + EmbeddedEntity::Arc(value) => EntityType::Arc(value.clone()), + EmbeddedEntity::Circle(value) => EntityType::Circle(value.clone()), + EmbeddedEntity::Ellipse(value) => EntityType::Ellipse(value.clone()), + EmbeddedEntity::Spline(value) => EntityType::Spline(value.clone()), + EmbeddedEntity::LwPolyline(value) => EntityType::LwPolyline(value.clone()), + EmbeddedEntity::Ray(value) => EntityType::Ray(value.clone()), + EmbeddedEntity::XLine(value) => EntityType::XLine(value.clone()), + EmbeddedEntity::Unknown { .. } => return None, + }) +} + +fn into_embedded_entity(value: EntityType) -> Option { + Some(match value { + EntityType::Point(value) => EmbeddedEntity::Point(value), + EntityType::Line(value) => EmbeddedEntity::Line(value), + EntityType::Arc(value) => EmbeddedEntity::Arc(value), + EntityType::Circle(value) => EmbeddedEntity::Circle(value), + EntityType::Ellipse(value) => EmbeddedEntity::Ellipse(value), + EntityType::Spline(value) => EmbeddedEntity::Spline(value), + EntityType::LwPolyline(value) => EmbeddedEntity::LwPolyline(value), + EntityType::Ray(value) => EmbeddedEntity::Ray(value), + EntityType::XLine(value) => EmbeddedEntity::XLine(value), + _ => return None, + }) +} + +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) + .normalize_or_zero(); + if !axis_point.is_finite() || axis.length_squared() <= 1e-12 { + 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 grip( id: usize, world: glam::DVec3, @@ -1873,6 +2002,16 @@ pub fn primitive_grips( GripShape::Square, None, ), + 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)); + } _ => {} } grips @@ -1883,6 +2022,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); + } + } let GripApply::Absolute(world) = apply else { return false; }; @@ -2032,6 +2176,9 @@ pub fn apply_primitive_grip( value.base.transform = (glam::DMat4::from_translation(delta) * current).to_cols_array(); } + SolidHistoryOperation::Revolve(value) if grip_id == GRIP_REVOLVE_AXIS => { + value.axis_point = acadrust::types::Vector3::new(local.x, local.y, local.z); + } _ => return false, } true diff --git a/src/scene/model/sweep_model.rs b/src/scene/model/sweep_model.rs index 247a8e5d1..aa96bf6a5 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}; use acadrust::objects::{ - SolidHistoryNodeBase, SolidHistoryOperation, SolidHistorySweep, + SolidHistoryNodeBase, SolidHistoryOperation, SolidHistoryRevolve, SolidHistorySweep, }; use acadrust::types::{Vector2, Vector3}; use acadrust::EntityType; @@ -139,6 +139,58 @@ pub fn revolved( ) } +fn profile_bounds_center(profile: &Profile) -> Option { + let mut low = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY); + let mut high = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY); + let mut found = false; + for piece in &profile.pieces { + for point in piece.tessellate_within(1e-4) { + let point = Vec3::from(profile.plane.point_at(point)); + if !point.is_finite() { + continue; + } + low.x = low.x.min(point.x); + low.y = low.y.min(point.y); + low.z = low.z.min(point.z); + high.x = high.x.max(point.x); + high.y = high.y.max(point.y); + high.z = high.z.max(point.z); + found = true; + } + } + found.then(|| (low + high) * 0.5) +} + +/// 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_bounds_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)?;