diff --git a/Cargo.lock b/Cargo.lock index 8736aab97..29579456e 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=67ff1f59f380afcfd939fa98883012a0c7b109f7#67ff1f59f380afcfd939fa98883012a0c7b109f7" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b0d8dfe6ae6d0bf239e4cc82ee644ba968f16ca4#b0d8dfe6ae6d0bf239e4cc82ee644ba968f16ca4" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index cf6913615..545ea4a76 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 = "67ff1f59f380afcfd939fa98883012a0c7b109f7", features = ["acis", "offset"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b0d8dfe6ae6d0bf239e4cc82ee644ba968f16ca4", 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/modules/model/primitive_cmd.rs b/src/modules/model/primitive_cmd.rs index c862fe8e9..895f494d1 100644 --- a/src/modules/model/primitive_cmd.rs +++ b/src/modules/model/primitive_cmd.rs @@ -67,6 +67,61 @@ enum SphereStep { }, } +#[derive(Clone, Copy)] +enum TorusStep { + Center, + Radius(DVec3), + Diameter(DVec3), + TwoPointFirst, + TwoPointSecond(DVec3), + ThreePointFirst, + ThreePointSecond(DVec3), + ThreePointThird(DVec3, DVec3), + TtrFirst, + TtrSecond { + object: TangentObject, + hit: DVec3, + }, + TtrRadius { + first: TangentObject, + second: TangentObject, + first_hit: DVec3, + second_hit: DVec3, + }, + TubeRadius { + center: DVec3, + major_radius: f64, + }, + TubeDiameter { + center: DVec3, + major_radius: f64, + }, + TubeTwoPointFirst { + center: DVec3, + major_radius: f64, + }, + TubeTwoPointSecond { + center: DVec3, + major_radius: f64, + first: DVec3, + }, +} + +#[derive(Clone, Copy)] +struct TorusDefaults { + major_radius: f64, + minor_radius: f64, +} + +impl Default for TorusDefaults { + fn default() -> Self { + Self { + major_radius: 100.0, + minor_radius: 25.0, + } + } +} + #[derive(Clone, Copy)] enum ConeStep { BaseCenter, @@ -180,6 +235,33 @@ fn remember_sphere_radius(radius: f64) { } } +fn torus_defaults_store() -> &'static Mutex { + static VALUE: OnceLock> = OnceLock::new(); + VALUE.get_or_init(|| Mutex::new(TorusDefaults::default())) +} + +fn torus_defaults() -> TorusDefaults { + torus_defaults_store() + .lock() + .map(|value| *value) + .unwrap_or_default() +} + +fn remember_torus_defaults(major_radius: f64, minor_radius: f64) { + if major_radius.is_finite() + && minor_radius.is_finite() + && major_radius > 1e-6 + && minor_radius > 1e-6 + { + if let Ok(mut value) = torus_defaults_store().lock() { + *value = TorusDefaults { + major_radius, + minor_radius, + }; + } + } +} + fn sphere_tangent_local(object: TangentObject, plane: WorkingPlane) -> TangentObject { match object { TangentObject::Line { p1, p2 } => TangentObject::Line { @@ -330,6 +412,8 @@ pub struct PrimitiveCommand { box_height_anchor: Option, sphere_step: SphereStep, sphere_default_radius: f64, + torus_step: TorusStep, + torus_defaults: TorusDefaults, cone_step: ConeStep, cone_frame: Option, cone_base_x_radius: f64, @@ -372,6 +456,8 @@ impl PrimitiveCommand { box_height_anchor: None, sphere_step: SphereStep::Center, sphere_default_radius: sphere_radius_default(), + torus_step: TorusStep::Center, + torus_defaults: torus_defaults(), cone_step: ConeStep::BaseCenter, cone_frame: None, cone_base_x_radius: remembered.base_x_radius, @@ -439,6 +525,81 @@ impl PrimitiveCommand { ))) } + fn set_torus_major_radius(&mut self, center: DVec3, major_radius: f64) -> CmdResult { + if !center.is_finite() || !major_radius.is_finite() || major_radius <= 1e-6 { + return CmdResult::NeedPoint; + } + self.torus_step = TorusStep::TubeRadius { + center, + major_radius, + }; + CmdResult::NeedPoint + } + + fn commit_torus( + &mut self, + center: DVec3, + major_radius: f64, + minor_radius: f64, + ) -> CmdResult { + if !center.is_finite() + || !major_radius.is_finite() + || !minor_radius.is_finite() + || major_radius <= 1e-6 + || minor_radius <= 1e-6 + { + return CmdResult::NeedPoint; + } + self.pts = vec![ + center, + center + DVec3::X * major_radius, + center + DVec3::X * minor_radius, + ]; + self.torus_defaults = TorusDefaults { + major_radius, + minor_radius, + }; + remember_torus_defaults(major_radius, minor_radius); + self.commit(0.0) + } + + fn torus_ttr_result(&mut self, radius: f64) -> CmdResult { + let TorusStep::TtrRadius { + first, + second, + first_hit, + second_hit, + } = self.torus_step + else { + return CmdResult::NeedPoint; + }; + let hint = (first_hit + second_hit) * 0.5; + let Some(center) = closest_sphere_center( + &sphere_ttr_centers(first, second, radius), + hint, + ) else { + return CmdResult::NeedPoint; + }; + self.set_torus_major_radius(center, radius) + } + + fn torus_preview( + &self, + center: DVec3, + major_radius: f64, + minor_radius: f64, + ) -> Option { + if !center.is_finite() + || !major_radius.is_finite() + || !minor_radius.is_finite() + || major_radius <= 1e-6 + || minor_radius <= 1e-6 + { + return None; + } + Some(self.place_preview(torus_wire(center, major_radius, minor_radius))) + } + fn cone_frame_at(&self, center: DVec3) -> WorkingPlane { WorkingPlane { origin: center, @@ -1867,15 +2028,11 @@ impl PrimitiveCommand { Shape::Pyramid => return None, Shape::Torus => { let c = self.pts[0]; - let first = (self.pts[1] - c).length(); - let second = (self.pts[2] - c).length(); - let outer = first.max(second); - let inner = first.min(second); - if inner < 1e-6 || outer - inner < 1e-6 { + let major = (self.pts[1] - c).length(); + let minor = (self.pts[2] - c).length(); + if major < 1e-6 || minor < 1e-6 { return None; } - let major = (outer + inner) * 0.5; - let minor = (outer - inner) * 0.5; let center = [c.x, c.y, c.z]; ( solid_model::torus_solid(center, major, minor), @@ -1985,12 +2142,40 @@ impl CadCommand for PrimitiveCommand { self.cone_step, ConeStep::TtrFirst | ConeStep::TtrSecond { .. } )) + || (self.shape == Shape::Torus + && matches!( + self.torus_step, + TorusStep::TtrFirst | TorusStep::TtrSecond { .. } + )) } fn on_tangent_point(&mut self, object: TangentObject, hit: DVec3) -> CmdResult { if self.shape == Shape::Cone { return self.on_cone_tangent(object, hit); } + if self.shape == Shape::Torus { + let object = sphere_tangent_local(object, self.plane); + let hit = self.plane.to_local(hit); + return match self.torus_step { + TorusStep::TtrFirst => { + self.torus_step = TorusStep::TtrSecond { object, hit }; + CmdResult::NeedPoint + } + TorusStep::TtrSecond { + object: first, + hit: first_hit, + } => { + self.torus_step = TorusStep::TtrRadius { + first, + second: object, + first_hit, + second_hit: hit, + }; + CmdResult::NeedPoint + } + _ => CmdResult::NeedPoint, + }; + } if self.shape != Shape::Sphere { return self.on_point(hit); } @@ -2093,13 +2278,69 @@ impl CadCommand for PrimitiveCommand { .into_owned(), }; } + if self.shape == Shape::Torus { + return match self.torus_step { + TorusStep::Center => { + t!("TORUS Specify center point or [3P/2P/Ttr]:").into_owned() + } + TorusStep::Radius(_) => crate::tf!( + "TORUS Specify radius or [Diameter] <{:.4}>:", + self.torus_defaults.major_radius + ) + .into_owned(), + TorusStep::Diameter(_) => crate::tf!( + "TORUS Specify diameter <{:.4}>:", + self.torus_defaults.major_radius * 2.0 + ) + .into_owned(), + TorusStep::TwoPointFirst => { + t!("TORUS Specify first end point of diameter:").into_owned() + } + TorusStep::TwoPointSecond(_) => { + t!("TORUS Specify second end point of diameter:").into_owned() + } + TorusStep::ThreePointFirst => { + t!("TORUS Specify first point on torus:").into_owned() + } + TorusStep::ThreePointSecond(_) => { + t!("TORUS Specify second point on torus:").into_owned() + } + TorusStep::ThreePointThird(_, _) => { + t!("TORUS Specify third point on torus:").into_owned() + } + TorusStep::TtrFirst => { + t!("TORUS Select first tangent object:").into_owned() + } + TorusStep::TtrSecond { .. } => { + t!("TORUS Select second tangent object:").into_owned() + } + TorusStep::TtrRadius { .. } => crate::tf!( + "TORUS Specify radius <{:.4}>:", + self.torus_defaults.major_radius + ) + .into_owned(), + TorusStep::TubeRadius { .. } => crate::tf!( + "TORUS Specify tube radius or [2Point/Diameter] <{:.4}>:", + self.torus_defaults.minor_radius + ) + .into_owned(), + TorusStep::TubeDiameter { .. } => crate::tf!( + "TORUS Specify tube diameter <{:.4}>:", + self.torus_defaults.minor_radius * 2.0 + ) + .into_owned(), + TorusStep::TubeTwoPointFirst { .. } => { + t!("TORUS Specify first end point of tube diameter:").into_owned() + } + TorusStep::TubeTwoPointSecond { .. } => { + t!("TORUS Specify second end point of tube diameter:").into_owned() + } + }; + } if self.height_step { return t!("%{n} Specify height :", n = n).into_owned(); } match (self.shape, self.pts.len()) { - (Shape::Torus, 0) => t!("%{n} Specify center point:", n = n).into_owned(), - (Shape::Torus, 1) => t!("%{n} Specify outer radius:", n = n).into_owned(), - (Shape::Torus, _) => t!("%{n} Specify inner radius:", n = n).into_owned(), (shape, 0) if shape.radial() => { t!("%{n} Specify center point:", n = n).into_owned() } @@ -2123,6 +2364,23 @@ impl CadCommand for PrimitiveCommand { _ => Vec::new(), }; } + if self.shape == Shape::Torus { + return match self.torus_step { + TorusStep::Center => vec![ + CmdOption::new("3P", "3P"), + CmdOption::new("2P", "2P"), + CmdOption::new("Ttr", "T"), + ], + TorusStep::Radius(_) => { + vec![CmdOption::new(t!("Diameter").as_ref(), "D")] + } + TorusStep::TubeRadius { .. } => vec![ + CmdOption::new(t!("2Point").as_ref(), "2P"), + CmdOption::new(t!("Diameter").as_ref(), "D"), + ], + _ => Vec::new(), + }; + } if self.shape == Shape::Cone { return self.cone_options(); } @@ -2187,6 +2445,76 @@ impl CadCommand for PrimitiveCommand { SphereStep::TtrFirst | SphereStep::TtrSecond { .. } => CmdResult::NeedPoint, }; } + if self.shape == Shape::Torus { + let local = self.plane.to_local(pt); + if !local.is_finite() { + return CmdResult::NeedPoint; + } + return match self.torus_step { + TorusStep::Center => { + self.torus_step = TorusStep::Radius(local); + CmdResult::NeedPoint + } + TorusStep::Radius(center) => { + self.set_torus_major_radius(center, center.distance(local)) + } + TorusStep::Diameter(center) => { + self.set_torus_major_radius(center, center.distance(local) * 0.5) + } + TorusStep::TwoPointFirst => { + self.torus_step = TorusStep::TwoPointSecond(local); + CmdResult::NeedPoint + } + TorusStep::TwoPointSecond(first) => self.set_torus_major_radius( + (first + local) * 0.5, + first.distance(local) * 0.5, + ), + TorusStep::ThreePointFirst => { + self.torus_step = TorusStep::ThreePointSecond(local); + CmdResult::NeedPoint + } + TorusStep::ThreePointSecond(first) => { + self.torus_step = TorusStep::ThreePointThird(first, local); + CmdResult::NeedPoint + } + TorusStep::ThreePointThird(first, second) => { + let Some((center, radius)) = + sphere_through_three_points(first, second, local) + else { + return CmdResult::NeedPoint; + }; + self.set_torus_major_radius(center, radius) + } + TorusStep::TtrRadius { second_hit, .. } => { + self.torus_ttr_result(second_hit.distance(local)) + } + TorusStep::TubeRadius { + center, + major_radius, + } => self.commit_torus(center, major_radius, center.distance(local)), + TorusStep::TubeDiameter { + center, + major_radius, + } => self.commit_torus(center, major_radius, center.distance(local) * 0.5), + TorusStep::TubeTwoPointFirst { + center, + major_radius, + } => { + self.torus_step = TorusStep::TubeTwoPointSecond { + center, + major_radius, + first: local, + }; + CmdResult::NeedPoint + } + TorusStep::TubeTwoPointSecond { + center, + major_radius, + first, + } => self.commit_torus(center, major_radius, first.distance(local) * 0.5), + TorusStep::TtrFirst | TorusStep::TtrSecond { .. } => CmdResult::NeedPoint, + }; + } if self.shape == Shape::Cone { return self.on_cone_point(pt); } @@ -2297,6 +2625,25 @@ impl CadCommand for PrimitiveCommand { _ => CmdResult::Cancel, }; } + if self.shape == Shape::Torus { + return match self.torus_step { + TorusStep::Radius(center) | TorusStep::Diameter(center) => { + self.set_torus_major_radius(center, self.torus_defaults.major_radius) + } + TorusStep::TtrRadius { .. } => { + self.torus_ttr_result(self.torus_defaults.major_radius) + } + TorusStep::TubeRadius { + center, + major_radius, + } + | TorusStep::TubeDiameter { + center, + major_radius, + } => self.commit_torus(center, major_radius, self.torus_defaults.minor_radius), + _ => CmdResult::Cancel, + }; + } if self.shape == Shape::Cone { return match self.cone_step { ConeStep::BaseRadius | ConeStep::BaseDiameter => { @@ -2393,6 +2740,17 @@ impl CadCommand for PrimitiveCommand { | SphereStep::TtrRadius { .. } ); } + if self.shape == Shape::Torus { + return matches!( + self.torus_step, + TorusStep::Center + | TorusStep::Radius(_) + | TorusStep::Diameter(_) + | TorusStep::TtrRadius { .. } + | TorusStep::TubeRadius { .. } + | TorusStep::TubeDiameter { .. } + ); + } if self.shape == Shape::Cone { return matches!( self.cone_step, @@ -2437,6 +2795,12 @@ impl CadCommand for PrimitiveCommand { if self.shape == Shape::Sphere { return matches!(self.sphere_step, SphereStep::Center | SphereStep::Radius(_)); } + if self.shape == Shape::Torus { + return matches!( + self.torus_step, + TorusStep::Center | TorusStep::Radius(_) | TorusStep::TubeRadius { .. } + ); + } if self.shape == Shape::Cone { return matches!( self.cone_step, @@ -2502,6 +2866,75 @@ impl CadCommand for PrimitiveCommand { _ => None, }; } + if self.shape == Shape::Torus { + let token = raw.trim(); + let upper = token.to_uppercase(); + return match self.torus_step { + TorusStep::Center if upper == "3P" => { + self.torus_step = TorusStep::ThreePointFirst; + Some(CmdResult::NeedPoint) + } + TorusStep::Center if upper == "2P" => { + self.torus_step = TorusStep::TwoPointFirst; + Some(CmdResult::NeedPoint) + } + TorusStep::Center if matches!(upper.as_str(), "T" | "TTR") => { + self.torus_step = TorusStep::TtrFirst; + Some(CmdResult::NeedPoint) + } + TorusStep::Radius(center) if matches!(upper.as_str(), "D" | "DIAMETER") => { + self.torus_step = TorusStep::Diameter(center); + Some(CmdResult::NeedPoint) + } + TorusStep::Radius(center) => { + let radius = crate::entities::common::parse_typed_length(token)?; + Some(self.set_torus_major_radius(center, radius)) + } + TorusStep::Diameter(center) => { + let diameter = crate::entities::common::parse_typed_length(token)?; + Some(self.set_torus_major_radius(center, diameter * 0.5)) + } + TorusStep::TtrRadius { .. } => { + let radius = crate::entities::common::parse_typed_length(token)?; + Some(self.torus_ttr_result(radius)) + } + TorusStep::TubeRadius { + center, + major_radius, + } if matches!(upper.as_str(), "2P" | "2POINT") => { + self.torus_step = TorusStep::TubeTwoPointFirst { + center, + major_radius, + }; + Some(CmdResult::NeedPoint) + } + TorusStep::TubeRadius { + center, + major_radius, + } if matches!(upper.as_str(), "D" | "DIAMETER") => { + self.torus_step = TorusStep::TubeDiameter { + center, + major_radius, + }; + Some(CmdResult::NeedPoint) + } + TorusStep::TubeRadius { + center, + major_radius, + } => { + let radius = crate::entities::common::parse_typed_length(token)?; + Some(self.commit_torus(center, major_radius, radius)) + } + TorusStep::TubeDiameter { + center, + major_radius, + } => { + let diameter = crate::entities::common::parse_typed_length(token)?; + Some(self.commit_torus(center, major_radius, diameter * 0.5)) + } + _ => None, + }; + } if self.shape == Shape::Cone { return self.on_cone_text(raw); } @@ -2556,6 +2989,63 @@ impl CadCommand for PrimitiveCommand { } fn on_mouse_move(&mut self, pt: DVec3) -> Option { + if self.shape == Shape::Torus { + let local = self.plane.to_local(pt); + if !local.is_finite() { + return None; + } + return match self.torus_step { + TorusStep::Radius(center) => self.torus_preview( + center, + center.distance(local), + self.torus_defaults.minor_radius, + ), + TorusStep::Diameter(center) => self.torus_preview( + center, + center.distance(local) * 0.5, + self.torus_defaults.minor_radius, + ), + TorusStep::TwoPointSecond(first) | TorusStep::ThreePointSecond(first) => { + self.torus_preview( + (first + local) * 0.5, + first.distance(local) * 0.5, + self.torus_defaults.minor_radius, + ) + } + TorusStep::ThreePointThird(first, second) => { + let (center, radius) = sphere_through_three_points(first, second, local)?; + self.torus_preview(center, radius, self.torus_defaults.minor_radius) + } + TorusStep::TtrRadius { + first, + second, + first_hit, + second_hit, + } => { + let radius = second_hit.distance(local); + let hint = (first_hit + second_hit) * 0.5; + let center = closest_sphere_center( + &sphere_ttr_centers(first, second, radius), + hint, + )?; + self.torus_preview(center, radius, self.torus_defaults.minor_radius) + } + TorusStep::TubeRadius { + center, + major_radius, + } => self.torus_preview(center, major_radius, center.distance(local)), + TorusStep::TubeDiameter { + center, + major_radius, + } => self.torus_preview(center, major_radius, center.distance(local) * 0.5), + TorusStep::TubeTwoPointSecond { + center, + major_radius, + first, + } => self.torus_preview(center, major_radius, first.distance(local) * 0.5), + _ => None, + }; + } if self.shape == Shape::Sphere { let local = self.plane.to_local(pt); if !local.is_finite() { @@ -2706,6 +3196,25 @@ impl CadCommand for PrimitiveCommand { fn dyn_spec(&self) -> Option { use crate::command::{DynAnchor, DynFieldSpec, DynGuide, DynRole, DynSpec}; + if self.shape == Shape::Torus { + let (anchor, role) = match self.torus_step { + TorusStep::Radius(center) => (center, DynRole::Radius), + TorusStep::Diameter(center) => (center, DynRole::Diameter), + TorusStep::TtrRadius { second_hit, .. } => { + (second_hit, DynRole::Radius) + } + TorusStep::TubeRadius { center, .. } => (center, DynRole::Radius), + TorusStep::TubeDiameter { center, .. } => (center, DynRole::Diameter), + _ => return None, + }; + return Some(DynSpec { + anchor: DynAnchor::Point(self.plane.to_world(anchor)), + fields: vec![DynFieldSpec::new(role)], + guide: DynGuide::Radius, + ref_point: None, + }); + } + if self.shape == Shape::Sphere { let (anchor, role) = match self.sphere_step { SphereStep::Radius(center) => (center, DynRole::Radius), @@ -2799,6 +3308,21 @@ impl CadCommand for PrimitiveCommand { } fn dyn_live_value(&self, cursor: DVec3) -> Option { + if self.shape == Shape::Torus { + let local = self.plane.to_local(cursor); + return match self.torus_step { + TorusStep::Radius(center) | TorusStep::TubeRadius { center, .. } => { + Some(center.distance(local)) + } + TorusStep::Diameter(center) | TorusStep::TubeDiameter { center, .. } => { + Some(center.distance(local)) + } + TorusStep::TtrRadius { second_hit, .. } => { + Some(second_hit.distance(local)) + } + _ => None, + }; + } if self.shape == Shape::Sphere { let local = self.plane.to_local(cursor); return match self.sphere_step { @@ -2882,6 +3406,48 @@ impl CadCommand for PrimitiveCommand { // ── Footprint preview ─────────────────────────────────────────────────────── +fn torus_wire(center: DVec3, major_radius: f64, minor_radius: f64) -> WireModel { + let mut points = Vec::new(); + let profile_limit = if minor_radius > major_radius { + std::f64::consts::PI - (major_radius / minor_radius).acos() + } else { + std::f64::consts::PI + }; + + for fraction in [-0.75_f64, -0.5, 0.0, 0.5, 0.75] { + let angle = profile_limit * fraction; + let ring_radius = major_radius + minor_radius * angle.cos(); + if ring_radius <= 1e-9 { + continue; + } + if !points.is_empty() { + points.push([f32::NAN; 3]); + } + circle_points( + &mut points, + center + DVec3::Z * (minor_radius * angle.sin()), + ring_radius, + ); + } + + for meridian in 0..12 { + points.push([f32::NAN; 3]); + let around = meridian as f64 * std::f64::consts::TAU / 12.0; + for sample in 0..=32 { + let profile = -profile_limit + 2.0 * profile_limit * sample as f64 / 32.0; + let radial = (major_radius + minor_radius * profile.cos()).max(0.0); + let point = center + + DVec3::new( + radial * around.cos(), + radial * around.sin(), + minor_radius * profile.sin(), + ); + points.push(point.as_vec3().to_array()); + } + } + wire("torus_preview", points) +} + fn footprint_wire(shape: Shape, pts: &[DVec3]) -> WireModel { let mut points: Vec<[f32; 3]> = Vec::new(); if shape == Shape::Pyramid { diff --git a/src/scene/model/solid_history.rs b/src/scene/model/solid_history.rs index a0b5a1879..7253255d6 100644 --- a/src/scene/model/solid_history.rs +++ b/src/scene/model/solid_history.rs @@ -101,6 +101,7 @@ pub fn has_specialized_primitive_properties( | SolidHistoryOperation::Sphere(_) | SolidHistoryOperation::Cone(_) | SolidHistoryOperation::Cylinder(_) + | SolidHistoryOperation::Torus(_) | SolidHistoryOperation::Pyramid(_) ) ) @@ -117,11 +118,81 @@ pub fn reference_point(operation: &SolidHistoryOperation) -> Option } SolidHistoryOperation::Cone(value) => world_point(value.base.transform, [0.0; 3]), SolidHistoryOperation::Cylinder(value) => world_point(value.base.transform, [0.0; 3]), + SolidHistoryOperation::Torus(value) => world_point(value.base.transform, [0.0; 3]), SolidHistoryOperation::Pyramid(value) => world_point(value.base.transform, [0.0; 3]), _ => None, } } +fn torus_properties( + document: &acadrust::CadDocument, + handle: acadrust::Handle, + value: &SolidHistoryTorus, +) -> Vec { + let Some(position) = world_point(value.base.transform, [0.0; 3]) else { + return Vec::new(); + }; + let (record_history, object_show_history, show_history_mode) = + history_flags(document, handle).unwrap_or((false, false, 1)); + let (show_history, _) = displayed_history_state(object_show_history, show_history_mode); + vec![ + PropSection { + title: t!("Geometry").into_owned(), + props: vec![ + Property { + label: t!("Solid type").into_owned(), + field: "solid_history_type", + value: PropValue::ReadOnly(t!("Torus").into_owned()), + }, + crate::entities::common::edit_prop( + t!("Position X").as_ref(), + PROP_POSITION_X, + position.x, + ), + crate::entities::common::edit_prop( + t!("Position Y").as_ref(), + PROP_POSITION_Y, + position.y, + ), + crate::entities::common::edit_prop( + t!("Position Z").as_ref(), + PROP_POSITION_Z, + position.z, + ), + history_prop( + t!("Torus radius").as_ref(), + PROP_MAJOR_RADIUS, + value.major_radius, + ), + history_prop( + t!("Tube radius").as_ref(), + PROP_MINOR_RADIUS, + value.minor_radius, + ), + ], + }, + PropSection { + title: t!("Solid History").into_owned(), + props: vec![ + Property { + label: t!("History").into_owned(), + field: PROP_HISTORY, + value: PropValue::ReadOnly( + if record_history { "Record" } else { "None" }.to_string(), + ), + }, + Property { + label: t!("Show History").into_owned(), + field: PROP_SHOW_HISTORY, + value: PropValue::ReadOnly( + if show_history { "Yes" } else { "No" }.to_string(), + ), + }, + ], + }, + ] +} + fn cone_properties( document: &acadrust::CadDocument, handle: acadrust::Handle, @@ -674,41 +745,20 @@ pub fn primitive_properties( let Some(operation) = document.solid_history_operation(handle) else { return Vec::new(); }; - let props = match operation { + match operation { SolidHistoryOperation::Box(value) => { - return rectangular_properties(document, handle, value, "Box") + rectangular_properties(document, handle, value, "Box") } SolidHistoryOperation::Wedge(value) => { - return rectangular_properties(document, handle, value, "Wedge") + rectangular_properties(document, handle, value, "Wedge") } - SolidHistoryOperation::Sphere(value) => { - return sphere_properties(document, handle, value) - } - SolidHistoryOperation::Cone(value) => return cone_properties(document, handle, value), - SolidHistoryOperation::Cylinder(value) => { - return cylinder_properties(document, handle, value) - } - SolidHistoryOperation::Pyramid(value) => { - return pyramid_properties(document, handle, value) - } - SolidHistoryOperation::Torus(value) => vec![ - history_prop( - t!("Outer Radius").as_ref(), - PROP_OUTER_RADIUS, - value.major_radius + value.minor_radius, - ), - history_prop( - t!("Inner Radius").as_ref(), - PROP_INNER_RADIUS, - (value.major_radius - value.minor_radius).max(0.0), - ), - ], - _ => return Vec::new(), - }; - vec![PropSection { - title: t!("Primitive").into_owned(), - props, - }] + SolidHistoryOperation::Sphere(value) => sphere_properties(document, handle, value), + SolidHistoryOperation::Cone(value) => cone_properties(document, handle, value), + SolidHistoryOperation::Cylinder(value) => cylinder_properties(document, handle, value), + SolidHistoryOperation::Pyramid(value) => pyramid_properties(document, handle, value), + SolidHistoryOperation::Torus(value) => torus_properties(document, handle, value), + _ => Vec::new(), + } } pub fn is_primitive_property(field: &str) -> bool { @@ -941,6 +991,36 @@ fn apply_cylinder_position_property( Some(true) } +fn apply_torus_position_property( + value: &mut SolidHistoryTorus, + field: &str, + text: &str, +) -> Option { + let axis = match field { + PROP_POSITION_X => 0, + PROP_POSITION_Y => 1, + PROP_POSITION_Z => 2, + _ => return None, + }; + let target = crate::entities::common::parse_length(text)?; + if !target.is_finite() { + return Some(false); + } + let current = matrix(value.base.transform)?; + let origin = current.transform_point3(glam::DVec3::ZERO); + if !origin.is_finite() || (target - origin[axis]).abs() <= 1e-12 { + return Some(false); + } + let mut delta = glam::DVec3::ZERO; + delta[axis] = target - origin[axis]; + let updated = glam::DMat4::from_translation(delta) * current; + if !updated.is_finite() || updated.determinant().abs() <= 1e-12 { + return Some(false); + } + value.base.transform = updated.to_cols_array(); + Some(true) +} + fn canonicalize_cylinder_radii(value: &mut SolidHistoryCylinder) -> bool { if value.minor_radius <= value.major_radius { value.x_radius = value.major_radius; @@ -1099,6 +1179,11 @@ pub fn apply_primitive_property( return applied; } } + if let SolidHistoryOperation::Torus(torus_value) = operation { + if let Some(applied) = apply_torus_position_property(torus_value, field, value) { + return applied; + } + } if let SolidHistoryOperation::Pyramid(pyramid_value) = operation { if let Some(applied) = apply_pyramid_geometry_property(pyramid_value, field, value) { return applied; @@ -1228,33 +1313,27 @@ pub fn apply_primitive_property( } value.radius = radius; } - SolidHistoryOperation::Torus(value) => { - let outer = value.major_radius + value.minor_radius; - let inner = (value.major_radius - value.minor_radius).max(0.0); - match field { - PROP_OUTER_RADIUS => { - let Some(next_outer) = positive() else { - return false; - }; - if next_outer <= inner { - return false; - } - value.major_radius = (next_outer + inner) * 0.5; - value.minor_radius = (next_outer - inner) * 0.5; + SolidHistoryOperation::Torus(value) => match field { + PROP_MAJOR_RADIUS => { + let Some(radius) = positive() else { + return false; + }; + if (radius - value.major_radius).abs() <= 1e-12 { + return false; } - PROP_INNER_RADIUS => { - let Some(next_inner) = positive() else { - return false; - }; - if next_inner >= outer { - return false; - } - value.major_radius = (outer + next_inner) * 0.5; - value.minor_radius = (outer - next_inner) * 0.5; - } - _ => return false, + value.major_radius = radius; } - } + PROP_MINOR_RADIUS => { + let Some(radius) = positive() else { + return false; + }; + if (radius - value.minor_radius).abs() <= 1e-12 { + return false; + } + value.minor_radius = radius; + } + _ => return false, + }, SolidHistoryOperation::Pyramid(value) => match field { PROP_BASE_RADIUS => { let Some(displayed) = positive() else { @@ -1555,22 +1634,18 @@ pub fn primitive_grips( ), SolidHistoryOperation::Torus(value) => { add( - GRIP_OUTER_RADIUS, + GRIP_MAJOR_RADIUS, value.base.transform, - [value.major_radius + value.minor_radius, 0.0, 0.0], + [value.major_radius, 0.0, 0.0], GripShape::Square, None, ); add( - GRIP_INNER_RADIUS, + GRIP_MINOR_RADIUS, value.base.transform, - [ - (value.major_radius - value.minor_radius).max(0.0), - 0.0, - 0.0, - ], + [value.major_radius, 0.0, value.minor_radius], GripShape::Square, - None, + Some([0.0, 0.0, 1.0]), ); } SolidHistoryOperation::Pyramid(value) => { @@ -1728,24 +1803,8 @@ pub fn apply_primitive_grip( value.radius = local.length().max(1e-6); } SolidHistoryOperation::Torus(value) => match grip_id { - GRIP_OUTER_RADIUS => { - let outer = local.x.hypot(local.y).max(1e-6); - let inner = (value.major_radius - value.minor_radius).max(1e-6); - if outer <= inner { - return false; - } - value.major_radius = (outer + inner) * 0.5; - value.minor_radius = (outer - inner) * 0.5; - } - GRIP_INNER_RADIUS => { - let inner = local.x.hypot(local.y).max(1e-6); - let outer = value.major_radius + value.minor_radius; - if inner >= outer { - return false; - } - value.major_radius = (outer + inner) * 0.5; - value.minor_radius = (outer - inner) * 0.5; - } + GRIP_MAJOR_RADIUS => value.major_radius = local.x.hypot(local.y).max(1e-6), + GRIP_MINOR_RADIUS => value.minor_radius = local.z.abs().max(1e-6), _ => return false, }, SolidHistoryOperation::Pyramid(value) => match grip_id {