From 8ec47061fe2f0c29b797fe75d216344a9f64d948 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Tue, 1 Sep 2026 23:10:40 +0300 Subject: [PATCH 1/3] Align polysolid sweep properties --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/app/model_ops.rs | 1 + src/scene/model/solid_history.rs | 166 +++++++++++++++++++++---------- 4 files changed, 118 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5cbecfa82..02d139c43 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=d8258b2768c781f5318acbc2126ddcd1664e21d2#d8258b2768c781f5318acbc2126ddcd1664e21d2" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=39720e3ac907307a653e93ee654afa63b7e4aa76#39720e3ac907307a653e93ee654afa63b7e4aa76" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index cd58c11c1..be01c3f0e 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 = "d8258b2768c781f5318acbc2126ddcd1664e21d2", features = ["acis", "offset"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "39720e3ac907307a653e93ee654afa63b7e4aa76", 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/model_ops.rs b/src/app/model_ops.rs index b92a40783..5f9be1cc6 100644 --- a/src/app/model_ops.rs +++ b/src/app/model_ops.rs @@ -23,6 +23,7 @@ impl super::OpenCADStudio { let EntityType::Solid3D(inner) = &mut entity else { return Handle::NULL; }; + inner.common.plotstyle_flags = 2; inner.wires = solid_model::edge_wires(&solid); let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&solid) else { diff --git a/src/scene/model/solid_history.rs b/src/scene/model/solid_history.rs index d1f2a59b3..55c36e63f 100644 --- a/src/scene/model/solid_history.rs +++ b/src/scene/model/solid_history.rs @@ -51,6 +51,9 @@ pub const PROP_POSITION_X: &str = "solid_history_position_x"; pub const PROP_POSITION_Y: &str = "solid_history_position_y"; pub const PROP_POSITION_Z: &str = "solid_history_position_z"; pub const PROP_ROTATION: &str = "solid_history_rotation"; +pub const PROP_PROFILE_ROTATION: &str = "solid_history_profile_rotation"; +pub const PROP_TWIST_ALONG_PATH: &str = "solid_history_twist_along_path"; +pub const PROP_SCALE_ALONG_PATH: &str = "solid_history_scale_along_path"; pub const PROP_HISTORY: &str = "solid_history_record"; pub const PROP_SHOW_HISTORY: &str = "solid_history_show"; @@ -133,14 +136,14 @@ fn sweep_properties( handle: acadrust::Handle, value: &SolidHistorySweep, ) -> Vec { - let Some(position) = reference_point(&SolidHistoryOperation::Sweep(value.clone())) 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, _) = + let (show_history, show_history_editable) = displayed_history_state(object_show_history, show_history_mode); let show_value = if show_history { "Yes" } else { "No" }; + let length = sweep_path_length(value) + .map(crate::entities::common::format_length) + .unwrap_or_default(); vec![ PropSection { title: t!("Geometry").into_owned(), @@ -150,21 +153,37 @@ fn sweep_properties( field: "solid_history_type", value: PropValue::ReadOnly(t!("Sweep").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, - ), + Property { + label: t!("Profile rotation").into_owned(), + field: PROP_PROFILE_ROTATION, + value: PropValue::EditText( + crate::entities::common::format_direction(value.align_angle), + ), + }, + Property { + label: t!("Bank").into_owned(), + field: "solid_history_bank", + value: PropValue::ReadOnly( + if value.bank { "Yes" } else { "No" }.to_string(), + ), + }, + Property { + label: t!("Twist along path").into_owned(), + field: PROP_TWIST_ALONG_PATH, + value: PropValue::EditText( + crate::entities::common::format_angle(value.twist_angle), + ), + }, + Property { + label: t!("Scale along path").into_owned(), + field: PROP_SCALE_ALONG_PATH, + value: PropValue::EditText(value.scale_factor.to_string()), + }, + Property { + label: t!("Length").into_owned(), + field: "solid_history_sweep_length", + value: PropValue::ReadOnly(length), + }, ], }, PropSection { @@ -173,20 +192,59 @@ fn sweep_properties( Property { label: t!("History").into_owned(), field: PROP_HISTORY, - value: PropValue::ReadOnly( - if record_history { "Record" } else { "None" }.to_string(), - ), + value: PropValue::Choice { + selected: if record_history { "Record" } else { "None" }.to_string(), + options: vec!["None".to_string(), "Record".to_string()], + }, }, Property { label: t!("Show History").into_owned(), field: PROP_SHOW_HISTORY, - value: PropValue::ReadOnly(show_value.to_string()), + value: if show_history_editable { + PropValue::Choice { + selected: show_value.to_string(), + options: vec!["No".to_string(), "Yes".to_string()], + } + } else { + PropValue::ReadOnly(show_value.to_string()) + }, }, ], }, ] } +fn sweep_path_length(value: &SolidHistorySweep) -> Option { + use acadrust::entities::EmbeddedEntity; + use acadrust::EntityType; + + let entity = match value.path_entity.as_ref()? { + 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()), + _ => return None, + }; + let curve = crate::entities::curve::entity_curve(&entity)?; + let transform = matrix(value.path_entity_transform)?; + let x_scale = transform + .transform_vector3(glam::DVec3::from_array(curve.plane.x_axis)) + .length(); + let y_scale = transform + .transform_vector3(glam::DVec3::from_array(curve.plane.y_axis)) + .length(); + if !x_scale.is_finite() + || !y_scale.is_finite() + || (x_scale - y_scale).abs() > 1e-9 * x_scale.max(y_scale).max(1.0) + { + return None; + } + let length = curve.curve.length() * (x_scale + y_scale) * 0.5; + (length.is_finite() && length >= 0.0).then_some(length) +} + fn cone_properties( document: &acadrust::CadDocument, handle: acadrust::Handle, @@ -678,6 +736,9 @@ pub fn is_primitive_property(field: &str) -> bool { | PROP_POSITION_Y | PROP_POSITION_Z | PROP_ROTATION + | PROP_PROFILE_ROTATION + | PROP_TWIST_ALONG_PATH + | PROP_SCALE_ALONG_PATH ) } @@ -884,38 +945,41 @@ fn apply_cylinder_position_property( Some(true) } -fn apply_sweep_position_property( +fn apply_sweep_geometry_property( value: &mut SolidHistorySweep, 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); + match field { + PROP_PROFILE_ROTATION => { + let target = crate::entities::common::parse_direction(text)?; + if !target.is_finite() || (target - value.align_angle).abs() <= 1e-12 { + return Some(false); + } + value.align_angle = target; + Some(true) + } + PROP_TWIST_ALONG_PATH => { + let target = crate::entities::common::parse_angle(text)?; + if !target.is_finite() || (target - value.twist_angle).abs() <= 1e-12 { + return Some(false); + } + value.twist_angle = target; + Some(true) + } + PROP_SCALE_ALONG_PATH => { + let target = text.trim().replace(',', ".").parse::().ok()?; + if !target.is_finite() + || target <= 1e-9 + || (target - value.scale_factor).abs() <= 1e-12 + { + return Some(false); + } + value.scale_factor = target; + Some(true) + } + _ => None, } - let current = matrix(value.base.transform)?; - let reference = current.transform_point3(glam::DVec3::new( - value.reference_point.x, - value.reference_point.y, - value.reference_point.z, - )); - if !reference.is_finite() || (target - reference[axis]).abs() <= 1e-12 { - return Some(false); - } - let mut delta = glam::DVec3::ZERO; - delta[axis] = target - reference[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 { @@ -963,7 +1027,7 @@ pub fn apply_primitive_property( } } if let SolidHistoryOperation::Sweep(sweep_value) = operation { - if let Some(applied) = apply_sweep_position_property(sweep_value, field, value) { + if let Some(applied) = apply_sweep_geometry_property(sweep_value, field, value) { return applied; } } From 5b26fc938ef712f9444c2f266c320a56951bcdc4 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Tue, 1 Sep 2026 23:30:58 +0300 Subject: [PATCH 2/3] Keep derived sweep rows visible --- src/app/properties.rs | 4 +--- src/scene/model/solid_history.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/properties.rs b/src/app/properties.rs index d0038380e..8c95cfa38 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -3066,9 +3066,7 @@ fn retain_specialized_primitive_sections( | "transparency" | "hyperlink" | "material" - | "solid_history_type" - ) || crate::scene::model::solid_history::is_primitive_property(property.field) - || crate::scene::model::solid_history::is_history_choice(property.field) + ) || crate::scene::model::solid_history::is_specialized_property(property.field) }); }); sections.retain(|section| !section.props.is_empty()); diff --git a/src/scene/model/solid_history.rs b/src/scene/model/solid_history.rs index 0e96f188c..4a9acea96 100644 --- a/src/scene/model/solid_history.rs +++ b/src/scene/model/solid_history.rs @@ -53,8 +53,10 @@ pub const PROP_POSITION_Y: &str = "solid_history_position_y"; pub const PROP_POSITION_Z: &str = "solid_history_position_z"; pub const PROP_ROTATION: &str = "solid_history_rotation"; pub const PROP_PROFILE_ROTATION: &str = "solid_history_profile_rotation"; +pub const PROP_BANK: &str = "solid_history_bank"; pub const PROP_TWIST_ALONG_PATH: &str = "solid_history_twist_along_path"; pub const PROP_SCALE_ALONG_PATH: &str = "solid_history_scale_along_path"; +pub const PROP_SWEEP_LENGTH: &str = "solid_history_sweep_length"; pub const PROP_PYRAMID_TYPE: &str = "solid_history_pyramid_type"; pub const PROP_HISTORY: &str = "solid_history_record"; pub const PROP_SHOW_HISTORY: &str = "solid_history_show"; @@ -168,7 +170,7 @@ fn sweep_properties( }, Property { label: t!("Bank").into_owned(), - field: "solid_history_bank", + field: PROP_BANK, value: PropValue::ReadOnly( if value.bank { "Yes" } else { "No" }.to_string(), ), @@ -187,7 +189,7 @@ fn sweep_properties( }, Property { label: t!("Length").into_owned(), - field: "solid_history_sweep_length", + field: PROP_SWEEP_LENGTH, value: PropValue::ReadOnly(length), }, ], @@ -923,6 +925,12 @@ pub fn is_history_choice(field: &str) -> bool { matches!(field, PROP_HISTORY | PROP_SHOW_HISTORY) } +pub fn is_specialized_property(field: &str) -> bool { + matches!(field, "solid_history_type" | PROP_BANK | PROP_SWEEP_LENGTH) + || is_primitive_property(field) + || is_history_choice(field) +} + pub fn apply_history_choice( document: &mut acadrust::CadDocument, handle: acadrust::Handle, From 5c7e8976e190e6ebd4998951c43af8fd324f7c3a Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 2 Sep 2026 00:51:20 +0300 Subject: [PATCH 3/3] fix(sweep): validate property geometry --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/scene/model/solid_history.rs | 28 +++++++++++++++------------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e1f35d15..df993e5a8 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=349823877944b17d5909e547e900c6f5d85e087b#349823877944b17d5909e547e900c6f5d85e087b" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=a809cb32cc6d90fd0e9c4199c3b4977cd56b7fa8#a809cb32cc6d90fd0e9c4199c3b4977cd56b7fa8" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index 0a2155822..6a5e39342 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 = "349823877944b17d5909e547e900c6f5d85e087b", features = ["acis", "offset"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "a809cb32cc6d90fd0e9c4199c3b4977cd56b7fa8", 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/scene/model/solid_history.rs b/src/scene/model/solid_history.rs index 4a9acea96..3542bf6dd 100644 --- a/src/scene/model/solid_history.rs +++ b/src/scene/model/solid_history.rs @@ -165,7 +165,7 @@ fn sweep_properties( label: t!("Profile rotation").into_owned(), field: PROP_PROFILE_ROTATION, value: PropValue::EditText( - crate::entities::common::format_direction(value.align_angle), + crate::entities::common::format_angle(value.align_angle), ), }, Property { @@ -236,20 +236,22 @@ fn sweep_path_length(value: &SolidHistorySweep) -> Option { _ => return None, }; let curve = crate::entities::curve::entity_curve(&entity)?; - let transform = matrix(value.path_entity_transform)?; - let x_scale = transform - .transform_vector3(glam::DVec3::from_array(curve.plane.x_axis)) - .length(); - let y_scale = transform - .transform_vector3(glam::DVec3::from_array(curve.plane.y_axis)) - .length(); - if !x_scale.is_finite() - || !y_scale.is_finite() - || (x_scale - y_scale).abs() > 1e-9 * x_scale.max(y_scale).max(1.0) + let transform = value.path_entity_transform; + if transform.iter().any(|value| !value.is_finite()) + || transform[3].abs() > 1e-9 + || transform[7].abs() > 1e-9 + || transform[11].abs() > 1e-9 + || (transform[15] - 1.0).abs() > 1e-9 { return None; } - let length = curve.curve.length() * (x_scale + y_scale) * 0.5; + let placement = cadkernel::brep::Placement { + x_axis: [transform[0], transform[1], transform[2]], + y_axis: [transform[4], transform[5], transform[6]], + z_axis: [transform[8], transform[9], transform[10]], + origin: [transform[12], transform[13], transform[14]], + }; + let length = curve.curve.length() * placement.scale()?; (length.is_finite() && length >= 0.0).then_some(length) } @@ -1137,7 +1139,7 @@ fn apply_sweep_geometry_property( ) -> Option { match field { PROP_PROFILE_ROTATION => { - let target = crate::entities::common::parse_direction(text)?; + let target = crate::entities::common::parse_angle(text)?; if !target.is_finite() || (target - value.align_angle).abs() <= 1e-12 { return Some(false); }