Harden revolve validation and properties

This commit is contained in:
ramox81 committed 2026-09-03 15:01:38 +03:00
1 parent 2e112f9aeb
commit 5bbb0bbe1f
8 files changed
+223 -26

No files matched your search

Generated
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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"] }
+19
View File
@@ -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
+19
View File
@@ -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
+19 -8
View File
@@ -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;
+19
View File
@@ -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")),
+32 -6
View File
@@ -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::<Vec<_>>();
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::<Vec<_>>();
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<Handle>) -> CmdResult {
CmdResult::NeedPoint
+113 -10
View File
@@ -113,6 +113,73 @@ fn displayed_history_state(object_show_history: bool, show_history_mode: i16) ->
}
}
fn revolve_profile_world_normal(value: &SolidHistoryRevolve) -> Option<glam::DVec3> {
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::<f64>().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)