From 731df4a72566d1b5af98d4c075b23928c094f477 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sun, 26 Jul 2026 19:30:43 +0300 Subject: [PATCH] feat(cad): integrate standard codec coverage Render, edit, query, select, and explode newly decoded standard entity variants. Pin acadifc de8d6b5 and preserve None color semantics. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/app/command_driver.rs | 16 ++ src/app/commands/inquiry.rs | 2 + src/app/helpers.rs | 1 + src/app/view/overlay.rs | 5 +- src/entities/dimension.rs | 295 ++++++++++++++++++++++++++++- src/entities/names.rs | 4 + src/entities/traits.rs | 3 +- src/io/mod.rs | 14 -- src/modules/draw/modify/explode.rs | 139 +++++++++++++- src/scene/camera_ops.rs | 52 +++-- src/scene/convert/tess.rs | 4 +- src/scene/model/image_model.rs | 3 +- src/scene/selection.rs | 9 +- src/ui/color_select.rs | 2 + src/ui/properties.rs | 1 + src/ui/style/mlstyle.rs | 1 + src/ui/window/attribute_editor.rs | 1 + 19 files changed, 484 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1266847e..4abfc97c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.0" -source = "git+https://github.com/OpenAEC-Foundation/acadifc.git?rev=8cc4793#8cc479342635fe16694e17226670d800cb3a1dfe" +source = "git+https://github.com/OpenAEC-Foundation/acadifc.git?rev=de8d6b5#de8d6b5e262468fa8292c0bb7ecf4fbcb042d709" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 736afa12d..53edbe2c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_Window [patch.crates-io] # Track the verified DWG round-trip, I/O, and unified PERF fixes. -acadrust = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "8cc4793" } +acadrust = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "de8d6b5" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Native enables the plugin host runtime (out-of-process plugins). diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index fe39ed51f..fbd9f1d02 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -1750,6 +1750,22 @@ impl OpenCADStudio { stretched |= mv(&mut d.feature_location); stretched |= mv(&mut d.leader_endpoint); } + Dimension::Arc(d) => { + stretched |= mv(&mut d.definition_point); + stretched |= mv(&mut d.first_extension_point); + stretched |= mv(&mut d.second_extension_point); + stretched |= mv(&mut d.center_point); + if d.has_leader { + stretched |= mv(&mut d.first_leader_point); + stretched |= mv(&mut d.second_leader_point); + } + } + Dimension::LargeRadial(d) => { + stretched |= mv(&mut d.definition_point); + stretched |= mv(&mut d.chord_point); + stretched |= mv(&mut d.override_center); + stretched |= mv(&mut d.jog_point); + } } // Pinned text follows too; the zero sentinel means // "auto placement" and must not be captured by a diff --git a/src/app/commands/inquiry.rs b/src/app/commands/inquiry.rs index bc1a823fc..0902123d6 100644 --- a/src/app/commands/inquiry.rs +++ b/src/app/commands/inquiry.rs @@ -1323,6 +1323,8 @@ fn add_selected_verb(entity: &acadrust::EntityType) -> Option<&'static str> { DimensionType::Diameter => "DIMDIAMETER", DimensionType::Radius => "DIMRADIUS", DimensionType::Ordinate => "DIMORDINATE", + DimensionType::ArcLength => "DIMARC", + DimensionType::LargeRadial => "DIMJOGGED", } } _ => return None, diff --git a/src/app/helpers.rs b/src/app/helpers.rs index 4e30d0685..dac59f1b4 100644 --- a/src/app/helpers.rs +++ b/src/app/helpers.rs @@ -383,6 +383,7 @@ pub(super) fn entity_type_key(entity: &acadrust::EntityType) -> String { Light(_) => "light", SectionSymbol(_) => "sectionsymbol", ViewBorder(_) => "viewborder", + Extended(entity) => return entity.class_name().to_ascii_lowercase(), Seqend(_) => "seqend", Unknown(_) => "unknown", } diff --git a/src/app/view/overlay.rs b/src/app/view/overlay.rs index aa2631bfd..db24def99 100644 --- a/src/app/view/overlay.rs +++ b/src/app/view/overlay.rs @@ -1117,7 +1117,7 @@ const QSELECT_ANY_PROP: &str = "(Any property)"; /// fields (Start X, Length, Radius, …) so type-specific filtering works. pub(super) fn qselect_overlay<'a>( state: &'a crate::app::QSelectState, - types: &[&'static str], + types: &[String], properties: &[(String, String)], ) -> Element<'a, Message> { use iced::widget::{checkbox, pick_list}; @@ -1165,7 +1165,7 @@ pub(super) fn qselect_overlay<'a>( }; let mut type_options: Vec = vec![QSELECT_ANY_TYPE.to_string()]; - type_options.extend(types.iter().map(|s| (*s).to_string())); + type_options.extend(types.iter().cloned()); let mut prop_options: Vec = vec![crate::app::QSelectPropertyChoice { @@ -1337,4 +1337,3 @@ pub(super) fn qselect_overlay<'a>( stack![catcher, centered].into() } - diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index 0b6f47594..50d758323 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -1,6 +1,7 @@ use acadrust::entities::{ - Dimension, DimensionAligned, DimensionAngular2Ln, DimensionAngular3Pt, DimensionBase, - DimensionDiameter, DimensionLinear, DimensionOrdinate, DimensionRadius, + Dimension, DimensionAligned, DimensionAngular2Ln, DimensionAngular3Pt, DimensionArc, + DimensionBase, DimensionDiameter, DimensionLargeRadial, DimensionLinear, DimensionOrdinate, + DimensionRadius, }; use acadrust::Entity; use glam::{DVec3, Vec3}; @@ -128,6 +129,47 @@ fn properties(dim: &Dimension) -> Vec { if d.is_ordinate_type_x { "X" } else { "Y" }, )); } + Dimension::Arc(d) => { + props.extend(angular_props( + d.center_point, + d.first_extension_point, + d.second_extension_point, + d.definition_point, + )); + props.push(edit( + "Arc Start (deg)", + "arc_start_parameter", + d.arc_start_parameter.to_degrees(), + )); + props.push(edit( + "Arc End (deg)", + "arc_end_parameter", + d.arc_end_parameter.to_degrees(), + )); + props.push(ro("Partial", "is_partial", d.is_partial.to_string())); + props.push(ro("Has Leader", "has_leader", d.has_leader.to_string())); + props.push(edit("Leader 1 X", "leader1_x", d.first_leader_point.x)); + props.push(edit("Leader 1 Y", "leader1_y", d.first_leader_point.y)); + props.push(edit("Leader 1 Z", "leader1_z", d.first_leader_point.z)); + props.push(edit("Leader 2 X", "leader2_x", d.second_leader_point.x)); + props.push(edit("Leader 2 Y", "leader2_y", d.second_leader_point.y)); + props.push(edit("Leader 2 Z", "leader2_z", d.second_leader_point.z)); + } + Dimension::LargeRadial(d) => { + props.push(edit("Center X", "definition_x", d.definition_point.x)); + props.push(edit("Center Y", "definition_y", d.definition_point.y)); + props.push(edit("Center Z", "definition_z", d.definition_point.z)); + props.push(edit("Chord X", "chord_x", d.chord_point.x)); + props.push(edit("Chord Y", "chord_y", d.chord_point.y)); + props.push(edit("Chord Z", "chord_z", d.chord_point.z)); + props.push(edit("Override Center X", "override_x", d.override_center.x)); + props.push(edit("Override Center Y", "override_y", d.override_center.y)); + props.push(edit("Override Center Z", "override_z", d.override_center.z)); + props.push(edit("Jog X", "jog_x", d.jog_point.x)); + props.push(edit("Jog Y", "jog_y", d.jog_point.y)); + props.push(edit("Jog Z", "jog_z", d.jog_point.z)); + props.push(edit_angle("Jog Angle", "jog_angle", d.jog_angle.to_degrees())); + } } vec![PropSection { title: "Geometry".into(), @@ -259,6 +301,8 @@ fn apply_geom_prop(dim: &mut Dimension, field: &str, value: &str) { Dimension::Angular2Ln(d) => apply_angular2_fields(d, field, value), Dimension::Angular3Pt(d) => apply_angular3_fields(d, field, value), Dimension::Ordinate(d) => apply_ordinate_fields(d, field, value), + Dimension::Arc(d) => apply_arc_fields(d, field, value), + Dimension::LargeRadial(d) => apply_large_radial_fields(d, field, value), } dim.base_mut().actual_measurement = dim.measurement(); } @@ -495,6 +539,89 @@ fn apply_ordinate_fields(d: &mut DimensionOrdinate, field: &str, value: &str) { } } +fn apply_arc_fields(d: &mut DimensionArc, field: &str, value: &str) { + apply_angular_common( + &mut d.center_point, + &mut d.first_extension_point, + &mut d.second_extension_point, + &mut d.definition_point, + field, + value, + ); + match field { + "arc_start_parameter" => { + let _ = assign_deg(value, &mut d.arc_start_parameter); + } + "arc_end_parameter" => { + let _ = assign_deg(value, &mut d.arc_end_parameter); + } + "leader1_x" => { + let _ = assign_f64(value, &mut d.first_leader_point.x); + } + "leader1_y" => { + let _ = assign_f64(value, &mut d.first_leader_point.y); + } + "leader1_z" => { + let _ = assign_f64(value, &mut d.first_leader_point.z); + } + "leader2_x" => { + let _ = assign_f64(value, &mut d.second_leader_point.x); + } + "leader2_y" => { + let _ = assign_f64(value, &mut d.second_leader_point.y); + } + "leader2_z" => { + let _ = assign_f64(value, &mut d.second_leader_point.z); + } + _ => {} + } +} + +fn apply_large_radial_fields(d: &mut DimensionLargeRadial, field: &str, value: &str) { + match field { + "definition_x" => { + let _ = assign_f64(value, &mut d.definition_point.x); + } + "definition_y" => { + let _ = assign_f64(value, &mut d.definition_point.y); + } + "definition_z" => { + let _ = assign_f64(value, &mut d.definition_point.z); + } + "chord_x" => { + let _ = assign_f64(value, &mut d.chord_point.x); + } + "chord_y" => { + let _ = assign_f64(value, &mut d.chord_point.y); + } + "chord_z" => { + let _ = assign_f64(value, &mut d.chord_point.z); + } + "override_x" => { + let _ = assign_f64(value, &mut d.override_center.x); + } + "override_y" => { + let _ = assign_f64(value, &mut d.override_center.y); + } + "override_z" => { + let _ = assign_f64(value, &mut d.override_center.z); + } + "jog_x" => { + let _ = assign_f64(value, &mut d.jog_point.x); + } + "jog_y" => { + let _ = assign_f64(value, &mut d.jog_point.y); + } + "jog_z" => { + let _ = assign_f64(value, &mut d.jog_point.z); + } + "jog_angle" => { + let _ = assign_deg(value, &mut d.jog_angle); + } + _ => {} + } +} + fn apply_transform(dim: &mut Dimension, t: &EntityTransform) { match t { EntityTransform::Translate(d) => dim.translate(acadrust::types::Vector3::new( @@ -556,6 +683,22 @@ where f(&mut d.feature_location); f(&mut d.leader_endpoint); } + Dimension::Arc(d) => { + f(&mut d.definition_point); + f(&mut d.first_extension_point); + f(&mut d.second_extension_point); + f(&mut d.center_point); + if d.has_leader { + f(&mut d.first_leader_point); + f(&mut d.second_leader_point); + } + } + Dimension::LargeRadial(d) => { + f(&mut d.definition_point); + f(&mut d.chord_point); + f(&mut d.override_center); + f(&mut d.jog_point); + } } } @@ -677,6 +820,29 @@ impl Grippable for Dimension { center_grip(2, dv3(&d.leader_endpoint)), center_grip(3, text), ], + Dimension::Arc(d) => { + let mut grips = vec![ + square_grip(0, dv3(&d.center_point)), + center_grip(1, dv3(&d.first_extension_point)), + center_grip(2, dv3(&d.second_extension_point)), + center_grip(3, dv3(&d.definition_point)), + ]; + if d.has_leader { + grips.push(center_grip(4, dv3(&d.first_leader_point))); + grips.push(center_grip(5, dv3(&d.second_leader_point))); + grips.push(center_grip(6, text)); + } else { + grips.push(center_grip(4, text)); + } + grips + } + Dimension::LargeRadial(d) => vec![ + square_grip(0, dv3(&d.definition_point)), + center_grip(1, dv3(&d.chord_point)), + center_grip(2, dv3(&d.override_center)), + center_grip(3, dv3(&d.jog_point)), + center_grip(4, text), + ], } } @@ -687,6 +853,8 @@ impl Grippable for Dimension { Dimension::Radius(_) | Dimension::Diameter(_) => 2, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => 4, Dimension::Ordinate(_) => 3, + Dimension::Arc(d) => if d.has_leader { 6 } else { 4 }, + Dimension::LargeRadial(_) => 4, }; if grip_id == text_grip { apply_to_v3(&mut self.base_mut().text_middle_point, &apply); @@ -739,6 +907,22 @@ impl Grippable for Dimension { 2 => apply_to_v3(&mut d.leader_endpoint, &apply), _ => {} }, + Dimension::Arc(d) => match grip_id { + 0 => apply_to_v3(&mut d.center_point, &apply), + 1 => apply_to_v3(&mut d.first_extension_point, &apply), + 2 => apply_to_v3(&mut d.second_extension_point, &apply), + 3 => apply_to_v3(&mut d.definition_point, &apply), + 4 => apply_to_v3(&mut d.first_leader_point, &apply), + 5 => apply_to_v3(&mut d.second_leader_point, &apply), + _ => {} + }, + Dimension::LargeRadial(d) => match grip_id { + 0 => apply_to_v3(&mut d.definition_point, &apply), + 1 => apply_to_v3(&mut d.chord_point, &apply), + 2 => apply_to_v3(&mut d.override_center, &apply), + 3 => apply_to_v3(&mut d.jog_point, &apply), + _ => {} + }, } self.base_mut().actual_measurement = self.measurement(); } @@ -750,6 +934,8 @@ impl Grippable for Dimension { Dimension::Radius(_) | Dimension::Diameter(_) => (1, 2), Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => (3, 4), Dimension::Ordinate(_) => (0, 3), + Dimension::Arc(d) => (3, if d.has_leader { 6 } else { 4 }), + Dimension::LargeRadial(_) => (3, 4), }; if grip_id == text_grip { vec![ @@ -816,6 +1002,8 @@ impl Grippable for Dimension { Dimension::Radius(_) | Dimension::Diameter(_) => (1, 2), Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => (3, 4), Dimension::Ordinate(_) => (0, 3), + Dimension::Arc(d) => (3, if d.has_leader { 6 } else { 4 }), + Dimension::LargeRadial(_) => (3, 4), }; match action { A::ResetText if grip_id == text_grip => { @@ -1860,6 +2048,7 @@ fn dimension_geometry( lv(d.dimension_arc), arrow1, arrow2, + None, ); } Dimension::Angular3Pt(d) => { @@ -1871,6 +2060,7 @@ fn dimension_geometry( lv(d.definition_point), arrow1, arrow2, + None, ); } Dimension::Ordinate(d) => { @@ -1885,10 +2075,74 @@ fn dimension_geometry( lv(d.leader_endpoint), ); } + Dimension::Arc(d) => { + append_angular_dimension( + &mut g, + lv(d.center_point), + lv(d.first_extension_point), + lv(d.second_extension_point), + lv(d.definition_point), + arrow1, + arrow2, + d.is_partial.then_some(( + d.arc_start_parameter as f32, + d.arc_end_parameter as f32, + )), + ); + if d.has_leader { + add_segment( + &mut g.dim_lines, + lv(d.first_leader_point), + lv(d.second_leader_point), + ); + } + } + Dimension::LargeRadial(d) => { + let chord = lv(d.chord_point); + let jog = lv(d.jog_point); + let override_center = lv(d.override_center); + let (near, far) = + jogged_radial_break(chord, jog, override_center, d.jog_angle as f32); + add_segment(&mut g.dim_lines, chord, near); + add_segment(&mut g.dim_lines, near, far); + add_segment(&mut g.dim_lines, far, override_center); + append_arrow( + &mut g, + chord, + normalized_or(near - chord, Vec3::X), + arrow1, + ); + } } g } +fn jogged_radial_break( + chord: Vec3, + jog: Vec3, + override_center: Vec3, + jog_angle: f32, +) -> (Vec3, Vec3) { + let radial = normalized_or(chord - override_center, Vec3::X); + let (sin, cos) = jog_angle.sin_cos(); + let transverse = normalized_or( + Vec3::new( + radial.x * cos - radial.y * sin, + radial.x * sin + radial.y * cos, + 0.0, + ), + Vec3::Y, + ); + let half = ((chord - override_center).length() * 0.04).max(1e-3); + let first = jog - transverse * half; + let second = jog + transverse * half; + if chord.distance_squared(first) <= chord.distance_squared(second) { + (first, second) + } else { + (second, first) + } +} + fn append_linear_dimension( g: &mut DimGeom, first: Vec3, @@ -2058,6 +2312,7 @@ fn append_angular_dimension( arc_point: Vec3, arrow1: &ArrowKind, arrow2: &ArrowKind, + explicit_sweep: Option<(f32, f32)>, ) { let radius = vertex.distance(arc_point); if radius <= 1e-6 { @@ -2067,18 +2322,19 @@ fn append_angular_dimension( } // Extension lines run from each measured point out to the dimension arc so // there is no gap between a ray and its arc endpoint. (#181 / DIM-027) - let dir1 = normalized_or(first - vertex, Vec3::X); - let dir2 = normalized_or(second - vertex, Vec3::X); + let measured_start = (first.y - vertex.y).atan2(first.x - vertex.x); + let measured_end = (second.y - vertex.y).atan2(second.x - vertex.x); + let (start, mut end) = explicit_sweep.unwrap_or((measured_start, measured_end)); + let dir1 = Vec3::new(start.cos(), start.sin(), 0.0); + let dir2 = Vec3::new(end.cos(), end.sin(), 0.0); add_segment(&mut g.ext_lines, first, vertex + dir1 * radius); add_segment(&mut g.ext_lines, second, vertex + dir2 * radius); - let start = (first.y - vertex.y).atan2(first.x - vertex.x); - let mut end = (second.y - vertex.y).atan2(second.x - vertex.x); let mut delta = end - start; while delta <= 0.0 { delta += std::f32::consts::TAU; } - if delta > std::f32::consts::PI { + if explicit_sweep.is_none() && delta > std::f32::consts::PI { end -= std::f32::consts::TAU; delta = end - start; } @@ -2142,6 +2398,25 @@ fn dimension_snap_pts(dim: &Dimension) -> Vec<(glam::DVec3, SnapHint)> { node(d.feature_location), node(d.leader_endpoint), ], + Dimension::Arc(d) => { + let mut points = vec![ + node(d.center_point), + node(d.first_extension_point), + node(d.second_extension_point), + node(d.definition_point), + ]; + if d.has_leader { + points.push(node(d.first_leader_point)); + points.push(node(d.second_leader_point)); + } + points + } + Dimension::LargeRadial(d) => vec![ + node(d.definition_point), + node(d.chord_point), + node(d.override_center), + node(d.jog_point), + ], } } @@ -2383,7 +2658,7 @@ fn dimension_text_parts( } else { let v = format_linear_value(dim.measurement(), style); match dim { - Dimension::Radius(_) => format!("R{}", v), + Dimension::Radius(_) | Dimension::LargeRadial(_) => format!("R{}", v), Dimension::Diameter(_) => format!("Ø{}", v), _ => v, } @@ -2858,6 +3133,8 @@ fn dimension_text_position(dim: &Dimension) -> Vec3 { Dimension::Angular2Ln(d) => lv(d.dimension_arc), Dimension::Angular3Pt(d) => lv(d.definition_point), Dimension::Ordinate(d) => lv(d.leader_endpoint), + Dimension::Arc(d) => lv(d.definition_point), + Dimension::LargeRadial(d) => lv(d.jog_point), } } @@ -3037,6 +3314,8 @@ fn dimension_text_pos_f64( Dimension::Angular2Ln(d) => d.dimension_arc, Dimension::Angular3Pt(d) => d.definition_point, Dimension::Ordinate(d) => d.leader_endpoint, + Dimension::Arc(d) => d.definition_point, + Dimension::LargeRadial(d) => d.jog_point, _ => base.text_middle_point, }; Vector3::new(mid.x, mid.y + perp_off * perp_sign_default(), mid.z) diff --git a/src/entities/names.rs b/src/entities/names.rs index bfcedd827..8e1460c93 100644 --- a/src/entities/names.rs +++ b/src/entities/names.rs @@ -57,6 +57,7 @@ pub fn ui_name(e: &EntityType) -> &'static str { EntityType::Light(_) => "Light", EntityType::SectionSymbol(_) => "Section Symbol", EntityType::ViewBorder(_) => "View Border", + EntityType::Extended(_) => "Extended Entity", EntityType::Seqend(_) => "Seqend", EntityType::Unknown(_) => "Unknown", } @@ -68,6 +69,9 @@ pub fn ui_name(e: &EntityType) -> &'static str { /// defers to [`ui_name`]. Falls back to "Unknown" for a class we couldn't name /// (the numeric `DWG_TYPE_` placeholder). pub fn ui_name_or_class(e: &EntityType) -> String { + if let EntityType::Extended(entity) = e { + return entity.class_name().to_string(); + } if let EntityType::Unknown(u) = e { let n = u.dxf_name.trim(); if !n.is_empty() && !n.starts_with("DWG_TYPE_") { diff --git a/src/entities/traits.rs b/src/entities/traits.rs index 0c16127e7..6dd37a556 100644 --- a/src/entities/traits.rs +++ b/src/entities/traits.rs @@ -97,7 +97,7 @@ pub trait TextContent { /// Human-readable variant name used by Quick Select / Select Similar /// filtering. Stable across releases — the strings here are the canonical /// type identifiers throughout the UI. -pub fn entity_type_name(et: &EntityType) -> &'static str { +pub fn entity_type_name(et: &EntityType) -> &str { match et { EntityType::Point(_) => "Point", EntityType::Line(_) => "Line", @@ -145,6 +145,7 @@ pub fn entity_type_name(et: &EntityType) -> &'static str { EntityType::Light(_) => "Light", EntityType::SectionSymbol(_) => "SectionSymbol", EntityType::ViewBorder(_) => "ViewBorder", + EntityType::Extended(entity) => entity.class_name(), EntityType::Unknown(_) => "Unknown", } } diff --git a/src/io/mod.rs b/src/io/mod.rs index 41e0fa1e3..ac6896e43 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -527,20 +527,6 @@ pub fn dropped_on_save_count( .dwg_source_version .is_some_and(|source| source != target_version) } - acadrust::EntityType::SectionSymbol(entity) => { - is_dxf - || entity.raw_dwg_data.is_none() - || entity - .dwg_source_version - .is_some_and(|source| source != target_version) - } - acadrust::EntityType::ViewBorder(entity) => { - is_dxf - || entity.raw_dwg_data.is_none() - || entity - .dwg_source_version - .is_some_and(|source| source != target_version) - } _ => false, }; if dropped { diff --git a/src/modules/draw/modify/explode.rs b/src/modules/draw/modify/explode.rs index c24586e77..47e3452b9 100644 --- a/src/modules/draw/modify/explode.rs +++ b/src/modules/draw/modify/explode.rs @@ -502,12 +502,23 @@ fn dim_arc_segs( while sweep <= -PI { sweep += 2.0 * PI; } + dim_arc_segs_with_sweep(center, radius, a1, sweep, common) +} + +fn dim_arc_segs_with_sweep( + center: Vector3, + radius: f64, + start: f64, + sweep: f64, + common: &acadrust::entities::EntityCommon, +) -> Vec { + use std::f64::consts::PI; let steps = 12usize.max((sweep.abs() / (PI / 36.0)).ceil() as usize); let pt = |a: f64| Vector3::new(center.x + radius * a.cos(), center.y + radius * a.sin(), center.z); let mut out = Vec::new(); - let mut prev = pt(a1); + let mut prev = pt(start); for i in 1..=steps { - let cur = pt(a1 + sweep * (i as f64 / steps as f64)); + let cur = pt(start + sweep * (i as f64 / steps as f64)); out.push(dim_seg(prev, cur, common)); prev = cur; } @@ -631,10 +642,12 @@ fn angular_block_segs( met: &DimMetrics, ext_c: &acadrust::entities::EntityCommon, dim_c: &acadrust::entities::EntityCommon, + explicit_sweep: Option<(f64, f64)>, ) -> Vec { use std::f64::consts::PI; - let a1 = (p1.y - vertex.y).atan2(p1.x - vertex.x); - let a2 = (p2.y - vertex.y).atan2(p2.x - vertex.x); + let measured_a1 = (p1.y - vertex.y).atan2(p1.x - vertex.x); + let measured_a2 = (p2.y - vertex.y).atan2(p2.x - vertex.x); + let (a1, a2) = explicit_sweep.unwrap_or((measured_a1, measured_a2)); let radius = ((arc_loc.x - vertex.x).powi(2) + (arc_loc.y - vertex.y).powi(2)).sqrt(); let mut out = Vec::new(); if radius < 1e-9 { @@ -647,14 +660,29 @@ fn angular_block_segs( let e2 = Vector3::new(vertex.x + a2.cos() * radius, vertex.y + a2.sin() * radius, vertex.z); out.push(dim_seg(p1, e1, ext_c)); out.push(dim_seg(p2, e2, ext_c)); - out.extend(dim_arc_segs(vertex, radius, a1, a2, dim_c)); - // Terminators tangent to the arc at each end, pointing along the sweep. let mut sweep = a2 - a1; - while sweep > PI { - sweep -= 2.0 * PI; + if explicit_sweep.is_some() { + while sweep <= 0.0 { + sweep += 2.0 * PI; + } + out.extend(dim_arc_segs_with_sweep( + vertex, + radius, + a1, + sweep, + dim_c, + )); + } else { + out.extend(dim_arc_segs(vertex, radius, a1, a2, dim_c)); } - while sweep <= -PI { - sweep += 2.0 * PI; + // Terminators tangent to the arc at each end, pointing along the sweep. + if explicit_sweep.is_none() { + while sweep > PI { + sweep -= 2.0 * PI; + } + while sweep <= -PI { + sweep += 2.0 * PI; + } } let sgn = if sweep >= 0.0 { 1.0 } else { -1.0 }; out.extend(dim_terminator(e1, -a1.sin() * sgn, a1.cos() * sgn, &met.arrow1, dim_c)); @@ -662,6 +690,49 @@ fn angular_block_segs( out } +fn jogged_radial_break( + chord: Vector3, + jog: Vector3, + override_center: Vector3, + jog_angle: f64, +) -> (Vector3, Vector3) { + let radial = norm2( + chord.x - override_center.x, + chord.y - override_center.y, + 1.0, + 0.0, + ); + let (sin, cos) = jog_angle.sin_cos(); + let transverse = ( + radial.0 * cos - radial.1 * sin, + radial.0 * sin + radial.1 * cos, + ); + let length = ((chord.x - override_center.x).powi(2) + + (chord.y - override_center.y).powi(2)) + .sqrt(); + let half = (length * 0.04).max(1e-6); + let first = Vector3::new( + jog.x - transverse.0 * half, + jog.y - transverse.1 * half, + jog.z, + ); + let second = Vector3::new( + jog.x + transverse.0 * half, + jog.y + transverse.1 * half, + jog.z, + ); + let distance_squared = |point: Vector3| { + (point.x - chord.x).powi(2) + + (point.y - chord.y).powi(2) + + (point.z - chord.z).powi(2) + }; + if distance_squared(first) <= distance_squared(second) { + (first, second) + } else { + (second, first) + } +} + /// The text anchor for a radial leader: the saved text middle point when set, /// else the midpoint of `a` and `b` — mirroring the live `dimension_text_position`. fn dim_text_anchor( @@ -902,6 +973,7 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec { &met, &ext_c, &dim_c, + None, )); } Dimension::Angular3Pt(d) => { @@ -913,12 +985,57 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec { &met, &ext_c, &dim_c, + None, )); } Dimension::Ordinate(d) => { result.push(make_seg(&d.feature_location, &d.definition_point, &dim_c)); result.push(make_seg(&d.definition_point, &d.leader_endpoint, &dim_c)); } + Dimension::Arc(d) => { + result.extend(angular_block_segs( + d.center_point, + d.first_extension_point, + d.second_extension_point, + d.definition_point, + &met, + &ext_c, + &dim_c, + d.is_partial.then_some(( + d.arc_start_parameter, + d.arc_end_parameter, + )), + )); + if d.has_leader { + result.push(make_seg( + &d.first_leader_point, + &d.second_leader_point, + &dim_c, + )); + } + } + Dimension::LargeRadial(d) => { + let (near, far) = jogged_radial_break( + d.chord_point, + d.jog_point, + d.override_center, + d.jog_angle, + ); + result.push(make_seg(&d.chord_point, &near, &dim_c)); + result.push(make_seg(&near, &far, &dim_c)); + result.push(make_seg(&far, &d.override_center, &dim_c)); + let len = ((near.x - d.chord_point.x).powi(2) + + (near.y - d.chord_point.y).powi(2)) + .sqrt() + .max(1e-12); + result.extend(dim_terminator( + d.chord_point, + (near.x - d.chord_point.x) / len, + (near.y - d.chord_point.y) / len, + &met.arrow1, + &dim_c, + )); + } } // Measurement text is the live render's own Text/MText entity (value, @@ -963,6 +1080,8 @@ fn sync_dimension_base_points_and_collect_pending(doc: &mut CadDocument) -> Vec< Dimension::Diameter(value) => Some(value.definition_point), Dimension::Ordinate(value) => Some(value.definition_point), Dimension::Angular3Pt(value) => Some(value.definition_point), + Dimension::Arc(value) => Some(value.definition_point), + Dimension::LargeRadial(value) => Some(value.definition_point), Dimension::Angular2Ln(_) => None, }; let base = dimension.base(); diff --git a/src/scene/camera_ops.rs b/src/scene/camera_ops.rs index 7abeb762d..6948e1d30 100644 --- a/src/scene/camera_ops.rs +++ b/src/scene/camera_ops.rs @@ -62,33 +62,31 @@ impl Scene { let eye_dir = cam.rotation * glam::Vec3::Z; let height = cam.ortho_size() * 2.0; let width = height; // caller can adjust; rough square - acadrust::tables::View { - handle: acadrust::types::Handle::NULL, - name: name.to_string(), - center: Vector3 { - x: cam.target.x as f64, - y: cam.target.y as f64, - z: 0.0, - }, - target: Vector3 { - x: cam.target.x as f64, - y: cam.target.y as f64, - z: cam.target.z as f64, - }, - direction: Vector3 { - x: eye_dir.x as f64, - y: eye_dir.y as f64, - z: eye_dir.z as f64, - }, - height: height as f64, - width: width as f64, - lens_length: 50.0, - front_clip: 0.0, - back_clip: 0.0, - twist_angle: 0.0, - // OCS saves an orthographic model view, not a perspective camera. - perspective: false, - } + let mut view = acadrust::tables::View::new(name); + view.center = Vector3 { + x: cam.target.x as f64, + y: cam.target.y as f64, + z: 0.0, + }; + view.target = Vector3 { + x: cam.target.x as f64, + y: cam.target.y as f64, + z: cam.target.z as f64, + }; + view.direction = Vector3 { + x: eye_dir.x as f64, + y: eye_dir.y as f64, + z: eye_dir.z as f64, + }; + view.height = height as f64; + view.width = width as f64; + view.lens_length = 50.0; + view.front_clip = 0.0; + view.back_clip = 0.0; + view.twist_angle = 0.0; + // OCS saves an orthographic model view, not a perspective camera. + view.perspective = false; + view } /// Zoom the model-space camera in/out by a percentage. diff --git a/src/scene/convert/tess.rs b/src/scene/convert/tess.rs index cc667ff8a..25fa64adb 100644 --- a/src/scene/convert/tess.rs +++ b/src/scene/convert/tess.rs @@ -37,7 +37,7 @@ fn section_arrow_dir_from_views( s: &acadrust::entities::SectionSymbol, ) -> Option<[f64; 2]> { use acadrust::types::Handle as AHandle; - if s.view_rep_handle == 0 { + if s.view_rep_handle.is_null() { return None; } // A view's active viewport, via its border entity among the ViewRep refs. @@ -47,7 +47,7 @@ fn section_arrow_dir_from_views( _ => None, } }; - let parent_vr = AHandle::from(s.view_rep_handle); + let parent_vr = s.view_rep_handle; let parent_refs = document.view_rep_refs.get(&parent_vr)?; let parent_vp_h = parent_refs.iter().find_map(border_vp)?; let sect_vr = parent_refs diff --git a/src/scene/model/image_model.rs b/src/scene/model/image_model.rs index c1f97187d..4958daec5 100644 --- a/src/scene/model/image_model.rs +++ b/src/scene/model/image_model.rs @@ -298,7 +298,8 @@ impl ImageModel { /// the frame is degenerate or nothing in the blob decodes, so the caller /// falls back to the frame placeholder. pub fn from_ole2frame(ole: &acadrust::entities::Ole2Frame) -> Option { - let (pixels, width, height) = super::ole_pres::decode(&ole.binary_data)?; + let payload = ole.encoded_payload(); + let (pixels, width, height) = super::ole_pres::decode(&payload)?; // Frame rectangle in WCS. `upper_left`/`lower_right` name the diagonal; // normalise to left/right/top/bottom so the bitmap sits upright. diff --git a/src/scene/selection.rs b/src/scene/selection.rs index 5d476df74..08d417435 100644 --- a/src/scene/selection.rs +++ b/src/scene/selection.rs @@ -55,7 +55,7 @@ impl Scene { if self.selected.is_empty() { return 0; } - let pairs: rustc_hash::FxHashSet<(&'static str, String)> = self + let pairs: rustc_hash::FxHashSet<(&str, String)> = self .selected .iter() .filter_map(|h| self.document.get_entity(*h)) @@ -182,13 +182,13 @@ impl Scene { /// Returns the sorted set of entity-type names present in the active /// layout. Used to populate the Quick Select "Object type" dropdown /// with only the types that actually exist in the drawing. - pub fn entity_type_names_in_layout(&self) -> Vec<&'static str> { + pub fn entity_type_names_in_layout(&self) -> Vec { use crate::entities::traits::entity_type_name; - let mut names: std::collections::BTreeSet<&'static str> = + let mut names: std::collections::BTreeSet = std::collections::BTreeSet::new(); for h in self.current_layout_entity_handles() { if let Some(e) = self.document.get_entity(h) { - names.insert(entity_type_name(e)); + names.insert(entity_type_name(e).to_string()); } } names.into_iter().collect() @@ -311,6 +311,7 @@ impl Scene { use acadrust::types::Color; match c { Color::ByLayer => "ByLayer".to_string(), + Color::None => "None".to_string(), Color::ByBlock => "ByBlock".to_string(), Color::Index(i) => i.to_string(), Color::Rgb { r, g, b } => format!("{},{},{}", r, g, b), diff --git a/src/ui/color_select.rs b/src/ui/color_select.rs index c42680e1c..08edb5c60 100644 --- a/src/ui/color_select.rs +++ b/src/ui/color_select.rs @@ -45,6 +45,7 @@ pub fn color_to_aci_string(c: AcadColor) -> String { match c { AcadColor::ByBlock => "0".to_string(), AcadColor::ByLayer => "256".to_string(), + AcadColor::None => "257".to_string(), AcadColor::Index(i) => i.to_string(), AcadColor::Rgb { .. } => "256".to_string(), } @@ -55,6 +56,7 @@ pub fn aci_string_to_color(s: &str) -> AcadColor { match s.trim().parse::().unwrap_or(256) { 0 => AcadColor::ByBlock, 256 => AcadColor::ByLayer, + 257 => AcadColor::None, n if (1..=255).contains(&n) => AcadColor::Index(n as u8), _ => AcadColor::ByLayer, } diff --git a/src/ui/properties.rs b/src/ui/properties.rs index 1889acd60..4a842feb1 100644 --- a/src/ui/properties.rs +++ b/src/ui/properties.rs @@ -1356,6 +1356,7 @@ fn picker_text_btn(label: &str, msg: Message) -> Element<'_, Message> { /// Returns an (iced::Color swatch_bg, display_label) pair for an AcadColor. pub fn acad_color_display(c: AcadColor) -> (Color, &'static str) { match c { + AcadColor::None => (Color::TRANSPARENT, "None"), AcadColor::ByLayer => ( Color { r: 0.35, diff --git a/src/ui/style/mlstyle.rs b/src/ui/style/mlstyle.rs index 7bb778dc9..bfe5ddb17 100644 --- a/src/ui/style/mlstyle.rs +++ b/src/ui/style/mlstyle.rs @@ -38,6 +38,7 @@ pub fn view_window<'a>( .map(|(idx, e)| { let color_str = match &e.color { acadrust::types::Color::ByLayer => "ByLayer".into(), + acadrust::types::Color::None => "None".into(), acadrust::types::Color::ByBlock => "ByBlock".into(), acadrust::types::Color::Index(i) => format!("ACI {i}"), acadrust::types::Color::Rgb { r, g, b } => format!("#{r:02X}{g:02X}{b:02X}"), diff --git a/src/ui/window/attribute_editor.rs b/src/ui/window/attribute_editor.rs index 1afb0b814..5c2efae05 100644 --- a/src/ui/window/attribute_editor.rs +++ b/src/ui/window/attribute_editor.rs @@ -116,6 +116,7 @@ const COLOR_OPTIONS: &[&str] = &[ pub fn color_label(c: AcadColor) -> String { match c { AcadColor::ByLayer => "ByLayer".into(), + AcadColor::None => "None".into(), AcadColor::ByBlock => "ByBlock".into(), AcadColor::Index(1) => "Red".into(), AcadColor::Index(2) => "Yellow".into(),