From 5bbb0bbe1f090c97e238fc93870fcef25938db11 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Thu, 3 Sep 2026 15:01:38 +0300 Subject: [PATCH] Harden revolve validation and properties --- Cargo.lock | 2 +- Cargo.toml | 2 +- locales/en-US/opencadstudio.ftl | 19 +++++ locales/tr-TR/opencadstudio.ftl | 19 +++++ src/app/command_driver.rs | 27 +++++-- src/locale_catalog.rs | 19 +++++ src/modules/insert/solid3d_cmds.rs | 38 +++++++-- src/scene/model/solid_history.rs | 123 ++++++++++++++++++++++++++--- 8 files changed, 223 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4544c3ff8..c25aab28c 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=284df21acdbc778e2f0fde7cbf47a35dae328920#284df21acdbc778e2f0fde7cbf47a35dae328920" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=7e6a651c9493bd52f6abcdaccf508d346766cda5#7e6a651c9493bd52f6abcdaccf508d346766cda5" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index 2ba64c829..872492c17 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 = "393d6c0857447f9ca678bed97a612707f0a842c6", features = ["serde"] } -cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "284df21acdbc778e2f0fde7cbf47a35dae328920", features = ["acis", "offset"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "7e6a651c9493bd52f6abcdaccf508d346766cda5", 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/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 7a73eebd1..1f1207e22 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -715,7 +715,9 @@ common = .quadrant = Quadrant .quality = Quality .revolve-could-not-revolve-profile = REVOLVE: could not revolve profile. + .revolve-created-objects-failed-sources = REVOLVE: __ocs_arg_created__ object(s) created; __ocs_arg_failed__ source(s) could not be revolved. .revolve-entity-not-found = REVOLVE: entity not found. + .revolve-no-selected-object-could-be-revolved = REVOLVE: no selected object could be revolved with the requested options. .revolve-solid-created-0 = REVOLVE: solid created (__ocs_fmt_0__°). .radians = Radians .radius = Radius @@ -2064,7 +2066,14 @@ insert = .revolve-angle-of-revolution-360 = REVOLVE Angle of revolution <360>: .revolve-axis-end-point = REVOLVE Axis end point: .revolve-axis-start-point = REVOLVE Axis start point: + .revolve-creation-mode-solid-surface = REVOLVE Creation mode [Solid/Surface] + .revolve-enter-angle-expression = REVOLVE Enter angle expression: + .revolve-select-line-ray-or-construction-line-for-axis = REVOLVE Select line, ray, or construction line for axis: + .revolve-select-objects-or-mode = REVOLVE Select objects to revolve or [Mode] (Enter to finish): .revolve-select-profile = REVOLVE Select profile: + .revolve-specify-angle-or-options = REVOLVE Specify angle of revolution or [Start angle/Reverse/Expression] + .revolve-specify-axis-start-or-options = REVOLVE Specify axis start point or [Object/X/Y/Z/Mode]: + .revolve-specify-start-angle = REVOLVE Specify start angle .sweep-select-path-line-arc-lwpolyline = SWEEP Select path (Line, Arc, LwPolyline): .sweep-select-profile-to-sweep = SWEEP Select profile to sweep: .set-base-point = @@ -2434,6 +2443,7 @@ properties = .alt-suppress-trailing-zeros = Alt suppress trailing zeros .ambient = Ambient .angle-format = Angle format + .angle-of-revolution = Angle of revolution .angle-precision = Angle precision .annotation-scale = Annotation scale .annotative-scale = Annotative scale @@ -2449,6 +2459,12 @@ properties = .at-least = At least .attachment-type = Attachment type .attachment = Attachment + .axis-direction-x = Axis direction X + .axis-direction-y = Axis direction Y + .axis-direction-z = Axis direction Z + .axis-position-x = Axis position X + .axis-position-y = Axis position Y + .axis-position-z = Axis position Z .arrowhead-size = Arrowhead Size .attenuation = Attenuation .attribute-definition = Attribute Definition @@ -2879,6 +2895,7 @@ properties = .show = Show .show-clipping = Show Clipping .show-cropping = Show Cropping + .show-history = Show History .show-intensity = Show Intensity .show-clipped = Show clipped .show-image = Show image @@ -2886,6 +2903,8 @@ properties = .singleline = SingleLine .softness = Softness .solid = Solid + .solid-history = Solid History + .solid-type = Solid type .solid3d = Solid3D .source-files = Source Files .source-block = Source block diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 7a757079b..8ba49644c 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -702,7 +702,9 @@ common = .quadrant = Çeyrek Nokta .quality = Kalite .revolve-could-not-revolve-profile = REVOLVE: Profil döndürülemedi. + .revolve-created-objects-failed-sources = REVOLVE: __ocs_arg_created__ nesne oluşturuldu; __ocs_arg_failed__ kaynak döndürülemedi. .revolve-entity-not-found = REVOLVE: Nesne bulunamadı. + .revolve-no-selected-object-could-be-revolved = REVOLVE: Seçilen nesnelerin hiçbiri istenen seçeneklerle döndürülemedi. .revolve-solid-created-0 = REVOLVE: Katı oluşturuldu (__ocs_fmt_0__°). .radians = Radyan .radius = Yarıçap @@ -2030,7 +2032,14 @@ insert = .revolve-angle-of-revolution-360 = REVOLVE Döndürme açısı <360>: .revolve-axis-end-point = REVOLVE Eksen bitiş noktası: .revolve-axis-start-point = REVOLVE Eksen başlangıç noktası: + .revolve-creation-mode-solid-surface = REVOLVE Oluşturma modu [Solid/Surface] + .revolve-enter-angle-expression = REVOLVE Açı ifadesini girin: + .revolve-select-line-ray-or-construction-line-for-axis = REVOLVE Eksen için çizgi, ışın veya sonsuz çizgi seçin: + .revolve-select-objects-or-mode = REVOLVE Döndürülecek nesneleri seçin veya [Mode] (bitirmek için Enter): .revolve-select-profile = REVOLVE Profili seçin: + .revolve-specify-angle-or-options = REVOLVE Döndürme açısını belirtin veya [Start angle/Reverse/Expression] + .revolve-specify-axis-start-or-options = REVOLVE Eksen başlangıç noktasını belirtin veya [Object/X/Y/Z/Mode]: + .revolve-specify-start-angle = REVOLVE Başlangıç açısını belirtin .sweep-select-path-line-arc-lwpolyline = SWEEP Yolu seçin (Çizgi, Yay, Hafif Çoklu Çizgi): .sweep-select-profile-to-sweep = SWEEP Süpürülecek profili seçin: .set-base-point = Taban Noktasını Ayarla @@ -2375,6 +2384,7 @@ properties = .alt-suppress-trailing-zeros = Alternatif birimlerde sondaki sıfırları gizle .ambient = Ortam .angle-format = Açı biçimi + .angle-of-revolution = Döndürme açısı .angle-precision = Açı hassasiyeti .annotation-scale = Açıklama ölçeği .annotative-scale = Açıklamalı ölçek @@ -2390,6 +2400,12 @@ properties = .at-least = En az .attachment-type = Bağlantı türü .attachment = Bağlantı + .axis-direction-x = Eksen yönü X + .axis-direction-y = Eksen yönü Y + .axis-direction-z = Eksen yönü Z + .axis-position-x = Eksen konumu X + .axis-position-y = Eksen konumu Y + .axis-position-z = Eksen konumu Z .arrowhead-size = Ok Ucu Boyutu .attenuation = Zayıflama .attribute-definition = Öznitelik Tanımı @@ -2819,6 +2835,7 @@ properties = .show = Göster .show-clipping = Kırpmayı Göster .show-cropping = Kırpmayı Göster + .show-history = Geçmişi Göster .show-intensity = Yoğunluğu Göster .show-clipped = Kırpılanı göster .show-image = Görüntüyü göster @@ -2826,6 +2843,8 @@ properties = .singleline = Tek Satır .softness = Yumuşaklık .solid = Katı + .solid-history = Katı Geçmişi + .solid-type = Katı türü .solid3d = 3B Katı .source-files = Kaynak Dosyalar .source-block = Kaynak blok diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 6572d4b5f..d863dc260 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -3831,12 +3831,11 @@ impl OpenCADStudio { mode, color: _, } => { - if handles.is_empty() - || handles - .iter() - .any(|handle| self.reject_locked_edit(i, *handle)) - { + if handles.is_empty() { self.tabs[i].active_cmd = None; + self.tabs[i].snap_result = None; + self.tabs[i].scene.clear_preview_wire(); + self.restore_pre_cmd_tangent(); return Task::none(); } let delete_sources = self.tabs[i].scene.document.header.delete_objects; @@ -3847,11 +3846,23 @@ impl OpenCADStudio { use crate::scene::model::sweep_model; let from = axis_start.to_array(); let to = axis_end.to_array(); - let pending = self.begin_undo(i, "REVOLVE", handles.len(), true); + let mut editable_handles = Vec::with_capacity(handles.len()); + let mut failed = 0usize; + for handle in handles { + if self.reject_locked_edit(i, handle) { + failed += 1; + } else { + editable_handles.push(handle); + } + } + let pending = if editable_handles.is_empty() { + None + } else { + self.begin_undo(i, "REVOLVE", editable_handles.len(), true) + }; let mut created_handles = Vec::new(); let mut consumed = Vec::new(); - let mut failed = 0usize; - for handle in &handles { + for handle in &editable_handles { let Some(entity) = self.tabs[i].scene.document.get_entity(*handle).cloned() else { failed += 1; diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 510dd9adf..0356c9e97 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -283,6 +283,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Angle = 0.0000° (degenerate rays)" => Some(("inquiry", "angle-0-0000-degenerate-rays")), "Angle format" => Some(("properties", "angle-format")), "Angle precision" => Some(("properties", "angle-precision")), + "Angle of revolution" => Some(("properties", "angle-of-revolution")), "Angles" => Some(("model", "angles")), "Angular" => Some(("annotate", "angular")), "Angular decimals (DIMADEC)" => Some(("common", "angular-decimals-dimadec")), @@ -357,6 +358,12 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Axis Point" => Some(("model", "axis-point")), "Axis Vector" => Some(("model", "axis-vector")), "Axis, End" => Some(("draw", "axis-end")), + "Axis direction X" => Some(("properties", "axis-direction-x")), + "Axis direction Y" => Some(("properties", "axis-direction-y")), + "Axis direction Z" => Some(("properties", "axis-direction-z")), + "Axis position X" => Some(("properties", "axis-position-x")), + "Axis position Y" => Some(("properties", "axis-position-y")), + "Axis position Z" => Some(("properties", "axis-position-z")), "BACKGROUND colour [Default/Black/DarkGray/Gray/LightGray/White] or R G B (0–255):" => Some(("file", "background-colour-default-black-darkgray-gray")), "BASE Specify base point:" => Some(("insert", "base-specify-base-point")), "BEDIT Select block reference to edit:" => Some(("modify", "bedit-select-block-reference-to-edit")), @@ -2177,9 +2184,18 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "REVOLVE Angle of revolution <360>:" => Some(("insert", "revolve-angle-of-revolution-360")), "REVOLVE Axis end point:" => Some(("insert", "revolve-axis-end-point")), "REVOLVE Axis start point:" => Some(("insert", "revolve-axis-start-point")), + "REVOLVE Creation mode [Solid/Surface]" => Some(("insert", "revolve-creation-mode-solid-surface")), + "REVOLVE Enter angle expression:" => Some(("insert", "revolve-enter-angle-expression")), + "REVOLVE Select line, ray, or construction line for axis:" => Some(("insert", "revolve-select-line-ray-or-construction-line-for-axis")), + "REVOLVE Select objects to revolve or [Mode] (Enter to finish):" => Some(("insert", "revolve-select-objects-or-mode")), "REVOLVE Select profile:" => Some(("insert", "revolve-select-profile")), + "REVOLVE Specify angle of revolution or [Start angle/Reverse/Expression]" => Some(("insert", "revolve-specify-angle-or-options")), + "REVOLVE Specify axis start point or [Object/X/Y/Z/Mode]:" => Some(("insert", "revolve-specify-axis-start-or-options")), + "REVOLVE Specify start angle" => Some(("insert", "revolve-specify-start-angle")), + "REVOLVE: created %{created} object(s); %{failed} source(s) could not be revolved." => Some(("common", "revolve-created-objects-failed-sources")), "REVOLVE: could not revolve profile." => Some(("common", "revolve-could-not-revolve-profile")), "REVOLVE: entity not found." => Some(("common", "revolve-entity-not-found")), + "REVOLVE: no selected object could be revolved with the requested options." => Some(("common", "revolve-no-selected-object-could-be-revolved")), "REVOLVE: solid created ({:.0}°)." => Some(("common", "revolve-solid-created-0")), "ROTATE Specify first reference point or type reference angle:" => Some(("modify", "rotate-specify-first-reference-point-or-type-r")), "ROTATE Specify new absolute angle [ref=%{a}]:" => Some(("modify", "rotate-specify-new-absolute-angle-ref-a")), @@ -2477,6 +2493,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Show Annotation Objects" => Some(("ui", "show-annotation-objects")), "Show Clipping" => Some(("properties", "show-clipping")), "Show Cropping" => Some(("properties", "show-cropping")), + "Show History" => Some(("properties", "show-history")), "Show Intensity" => Some(("properties", "show-intensity")), "Show Lineweight" => Some(("ui", "show-lineweight")), "Show Lineweight\nLWDISPLAY" => Some(("ui", "show-lineweight-lwdisplay")), @@ -2494,6 +2511,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Snap to\nUnderlays" => Some(("insert", "snap-to-underlays")), "Softness" => Some(("properties", "softness")), "Solid" => Some(("properties", "solid")), + "Solid History" => Some(("properties", "solid-history")), + "Solid type" => Some(("properties", "solid-type")), "Solid created." => Some(("common", "solid-created")), "Solid3D" => Some(("properties", "solid3d")), "Source Files" => Some(("properties", "source-files")), diff --git a/src/modules/insert/solid3d_cmds.rs b/src/modules/insert/solid3d_cmds.rs index be7bbf0f3..25e7ffb77 100644 --- a/src/modules/insert/solid3d_cmds.rs +++ b/src/modules/insert/solid3d_cmds.rs @@ -622,6 +622,7 @@ pub struct RevolveCommand { axis_end: DVec3, working_plane: WorkingPlane, mode: ExtrudeMode, + mode_return: RevolveStep, angle: f64, start_angle: f64, reverse: bool, @@ -670,6 +671,7 @@ impl RevolveCommand { axis_end: DVec3::new(0.0, 0.0, 1.0), working_plane: WorkingPlane::default(), mode: defaults.mode, + mode_return: RevolveStep::Pick, angle: defaults.angle, start_angle: defaults.start_angle, reverse: false, @@ -678,8 +680,18 @@ impl RevolveCommand { } pub fn set_preselection(&mut self, profiles: Vec<(Handle, EntityType)>) { - self.handles = profiles.iter().map(|(handle, _)| *handle).collect(); - self.preview_profiles = profiles; + let selected_handles = profiles.iter().map(|(handle, _)| *handle).collect::>(); + self.preview_profiles = profiles + .into_iter() + .filter(|(_, entity)| { + crate::scene::model::sweep_model::extrusion_profile_of(entity).is_some() + }) + .collect(); + self.handles = if self.preview_profiles.is_empty() { + Vec::new() + } else { + selected_handles + }; if !self.handles.is_empty() { self.step = RevolveStep::AxisStart; } @@ -844,7 +856,7 @@ impl CadCommand for RevolveCommand { t!("REVOLVE Creation mode [Solid/Surface]"), if self.mode == ExtrudeMode::Solid { "Solid" } else { "Surface" } ), - RevolveStep::AxisStart => t!("REVOLVE Specify axis start point or [Object/X/Y/Z]:").into_owned(), + RevolveStep::AxisStart => t!("REVOLVE Specify axis start point or [Object/X/Y/Z/Mode]:").into_owned(), RevolveStep::AxisEnd => t!("REVOLVE Axis end point:").into_owned(), RevolveStep::AxisObject => t!("REVOLVE Select line, ray, or construction line for axis:").into_owned(), RevolveStep::Angle => format!( @@ -872,6 +884,7 @@ impl CadCommand for RevolveCommand { CmdOption::new("X", "X"), CmdOption::new("Y", "Y"), CmdOption::new("Z", "Z"), + CmdOption::new("Mode", "MODE"), ], RevolveStep::Angle => vec![ CmdOption::new("Start angle", "START"), @@ -955,6 +968,7 @@ impl CadCommand for RevolveCommand { let upper = value.to_ascii_uppercase(); match self.step { RevolveStep::Pick if "MODE".starts_with(&upper) => { + self.mode_return = RevolveStep::Pick; self.step = RevolveStep::Mode; Some(CmdResult::NeedPoint) } @@ -970,13 +984,17 @@ impl CadCommand for RevolveCommand { .lock() .unwrap_or_else(|error| error.into_inner()) .mode = self.mode; - self.step = RevolveStep::Pick; + self.step = self.mode_return; Some(CmdResult::NeedPoint) } RevolveStep::AxisStart => { if "OBJECT".starts_with(&upper) { self.step = RevolveStep::AxisObject; Some(CmdResult::NeedPoint) + } else if "MODE".starts_with(&upper) { + self.mode_return = RevolveStep::AxisStart; + self.step = RevolveStep::Mode; + Some(CmdResult::NeedPoint) } else if upper == "X" { Some(self.set_axis(self.working_plane.x)) } else if upper == "Y" { @@ -1038,7 +1056,7 @@ impl CadCommand for RevolveCommand { CmdResult::NeedPoint } RevolveStep::Mode => { - self.step = RevolveStep::Pick; + self.step = self.mode_return; CmdResult::NeedPoint } RevolveStep::Angle => self.finish(self.angle), @@ -1062,11 +1080,19 @@ impl CadCommand for RevolveCommand { if self.step != RevolveStep::Pick { return; } - self.handles = entities.iter().map(|entry| entry.handle).collect(); + let selected_handles = entities.iter().map(|entry| entry.handle).collect::>(); self.preview_profiles = entities .into_iter() + .filter(|entry| { + crate::scene::model::sweep_model::extrusion_profile_of(&entry.entity).is_some() + }) .map(|entry| (entry.handle, entry.entity)) .collect(); + self.handles = if self.preview_profiles.is_empty() { + Vec::new() + } else { + selected_handles + }; } fn on_selection_complete(&mut self, _handles: Vec) -> CmdResult { CmdResult::NeedPoint diff --git a/src/scene/model/solid_history.rs b/src/scene/model/solid_history.rs index a9ecad5f2..b631f02b6 100644 --- a/src/scene/model/solid_history.rs +++ b/src/scene/model/solid_history.rs @@ -113,6 +113,73 @@ fn displayed_history_state(object_show_history: bool, show_history_mode: i16) -> } } +fn revolve_profile_world_normal(value: &SolidHistoryRevolve) -> Option { + let profile = value.sweep_entity.as_ref().and_then(embedded_entity)?; + let normal = crate::entities::curve::entity_curve(&profile)?.plane.normal()?; + let inverse_transpose = matrix(value.base.transform)?.inverse().transpose(); + inverse_transpose + .transform_vector3(glam::DVec3::from_array(normal)) + .try_normalize() +} + +fn revolve_position_component_editable(value: &SolidHistoryRevolve, axis: usize) -> bool { + revolve_profile_world_normal(value).is_some_and(|normal| normal[axis].abs() <= 1e-9) +} + +fn revolve_direction_component_capacity( + value: &SolidHistoryRevolve, + axis: usize, +) -> Option<(glam::DVec3, f64)> { + let normal = revolve_profile_world_normal(value)?; + let capacity = (1.0 - normal[axis] * normal[axis]).max(0.0).sqrt(); + Some((normal, capacity)) +} + +fn revolve_direction_component_editable(value: &SolidHistoryRevolve, axis: usize) -> bool { + revolve_direction_component_capacity(value, axis) + .is_some_and(|(_, capacity)| capacity > 1e-9) +} + +fn revolve_position_property( + value: &SolidHistoryRevolve, + axis: usize, + label: &str, + field: &'static str, + position: f64, +) -> Property { + if revolve_position_component_editable(value, axis) { + crate::entities::common::edit_prop(label, field, position) + } else { + Property { + label: label.to_string(), + field, + value: PropValue::ReadOnly(crate::entities::common::format_length(position)), + } + } +} + +fn revolve_direction_property( + value: &SolidHistoryRevolve, + axis: usize, + label: &str, + field: &'static str, + direction: f64, +) -> Property { + if revolve_direction_component_editable(value, axis) { + crate::entities::common::edit_scalar_prop(label, field, direction) + } else { + Property { + label: label.to_string(), + field, + value: PropValue::ReadOnly(if direction == 0.0 { + "0".to_string() + } else { + direction.to_string() + }), + } + } +} + pub fn has_specialized_primitive_properties( document: &acadrust::CadDocument, handle: acadrust::Handle, @@ -201,32 +268,44 @@ fn revolve_properties( value.revolve_angle, )), }, - crate::entities::common::edit_prop( + revolve_position_property( + value, + 0, t!("Axis position X").as_ref(), PROP_AXIS_POSITION_X, axis_position.x, ), - crate::entities::common::edit_prop( + revolve_position_property( + value, + 1, t!("Axis position Y").as_ref(), PROP_AXIS_POSITION_Y, axis_position.y, ), - crate::entities::common::edit_prop( + revolve_position_property( + value, + 2, t!("Axis position Z").as_ref(), PROP_AXIS_POSITION_Z, axis_position.z, ), - crate::entities::common::edit_scalar_prop( + revolve_direction_property( + value, + 0, t!("Axis direction X").as_ref(), PROP_AXIS_DIRECTION_X, axis_direction.x, ), - crate::entities::common::edit_scalar_prop( + revolve_direction_property( + value, + 1, t!("Axis direction Y").as_ref(), PROP_AXIS_DIRECTION_Y, axis_direction.y, ), - crate::entities::common::edit_scalar_prop( + revolve_direction_property( + value, + 2, t!("Axis direction Z").as_ref(), PROP_AXIS_DIRECTION_Z, axis_direction.z, @@ -1287,6 +1366,9 @@ fn apply_revolve_geometry_property( PROP_AXIS_POSITION_Y => 1, _ => 2, }; + if !revolve_position_component_editable(value, axis) { + return Some(false); + } let target = crate::entities::common::parse_length(text)?; if !target.is_finite() { return Some(false); @@ -1317,17 +1399,38 @@ fn apply_revolve_geometry_property( _ => 2, }; let target = text.trim().replace(',', ".").parse::().ok()?; - if !target.is_finite() { + let Some((normal, capacity)) = revolve_direction_component_capacity(value, axis) else { + return Some(false); + }; + if !target.is_finite() || capacity <= 1e-9 || target.abs() > capacity + 1e-12 { return Some(false); } - let mut world = world_vector( + let current_world = world_vector( value.base.transform, [value.direction.x, value.direction.y, value.direction.z], )?; - world[axis] = target; - let Some(world) = world.try_normalize() else { + let target = target.clamp(-capacity, capacity); + if (current_world[axis] - target).abs() <= 1e-12 { + return Some(false); + } + let component_axis = match axis { + 0 => glam::DVec3::X, + 1 => glam::DVec3::Y, + _ => glam::DVec3::Z, + }; + let Some(projected_axis) = (component_axis - normal * normal[axis]).try_normalize() else { return Some(false); }; + let other_axis = normal.cross(projected_axis); + let along_projected = target / capacity; + let remaining = (1.0 - along_projected * along_projected).max(0.0).sqrt(); + let other_sign = if current_world.dot(other_axis) < 0.0 { + -1.0 + } else { + 1.0 + }; + let world = + projected_axis * along_projected + other_axis * (other_sign * remaining); let Some(local) = matrix(value.base.transform)? .inverse() .transform_vector3(world)