From 0426df79ca0bbe7c822ae50f1588403010441e2f Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Thu, 10 Sep 2026 11:25:33 +0300 Subject: [PATCH] fix(section): add state grip menu --- src/app/update/viewport.rs | 6 ++- src/entities/extended.rs | 88 +++++++++++++++++++++++++++++++++----- src/scene/model/object.rs | 6 +++ src/scene/pick/grip.rs | 13 ++++-- src/ui/overlay.rs | 8 ++-- 5 files changed, 104 insertions(+), 17 deletions(-) diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index df4c7a11d..e35c6ea97 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -3049,7 +3049,11 @@ impl OpenCADStudio { return Task::none(); }; let grip_shape = self.tabs[i].selected_grips[grip_index].shape; - if grip_shape == crate::scene::model::object::GripShape::Dropdown { + if matches!( + grip_shape, + crate::scene::model::object::GripShape::Dropdown + | crate::scene::model::object::GripShape::DropdownAdjacent + ) { use crate::entities::traits::EntityTypeOps; let items = self.tabs[i] .scene diff --git a/src/entities/extended.rs b/src/entities/extended.rs index f79e5163b..55de63fe0 100644 --- a/src/entities/extended.rs +++ b/src/entities/extended.rs @@ -13,7 +13,7 @@ use crate::entities::common::{ use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible}; use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject}; use crate::scene::model::object::{ - GripApply, GripDef, PropSection, PropValue, Property, + GripApply, GripDef, GripMenuAction, GripMenuItem, PropSection, PropValue, Property, }; use crate::scene::model::wire_model::SnapHint; @@ -25,6 +25,7 @@ const SECTION_GRIP_RIGHT: usize = 100_001; const SECTION_GRIP_TOP: usize = 100_002; const SECTION_GRIP_BOTTOM: usize = 100_003; const SECTION_GRIP_NORMAL: usize = 100_004; +const SECTION_GRIP_STATE: usize = 100_005; pub(crate) fn section_is_slice(entity: &ExtendedEntity) -> bool { entity @@ -1584,6 +1585,19 @@ fn grips(entity: &ExtendedEntity) -> Vec { }) .collect() }; + if let Some(right) = data.vertices.last() { + let vertical = normalized(data.vertical_direction, Vector3::UNIT_Z); + let edge_center_offset = (data.top_height - data.bottom_height) * 0.5; + let anchor = *right + vertical * edge_center_offset; + grips.push(GripDef { + id: SECTION_GRIP_STATE, + world: glam::DVec3::new(anchor.x, anchor.y, anchor.z), + is_midpoint: false, + shape: crate::scene::model::object::GripShape::DropdownAdjacent, + dir: None, + axis: None, + }); + } if let Some(center) = section_center(data) { let normal = section_viewing_direction(data); let span = data @@ -1789,25 +1803,25 @@ fn section_plane_edge_grips(data: &SectionObjectData) -> Vec { SECTION_GRIP_LEFT, *first + vertical * edge_center_offset, vertical, - tangent, + None, ), ( SECTION_GRIP_RIGHT, *last + vertical * edge_center_offset, vertical, - tangent, + None, ), ( SECTION_GRIP_TOP, center + vertical * data.top_height, tangent, - vertical, + Some(vertical), ), ( SECTION_GRIP_BOTTOM, center - vertical * data.bottom_height, tangent, - vertical, + Some(vertical), ), ]; definitions @@ -1818,7 +1832,7 @@ fn section_plane_edge_grips(data: &SectionObjectData) -> Vec { is_midpoint: false, shape: crate::scene::model::object::GripShape::Rectangle, dir: Some(glam::DVec3::new(edge.x, edge.y, edge.z)), - axis: Some(glam::DVec3::new(axis.x, axis.y, axis.z)), + axis: axis.map(|axis| glam::DVec3::new(axis.x, axis.y, axis.z)), }) .collect() } @@ -1850,19 +1864,25 @@ fn apply_section_plane_edge_grip( }; let span = (*last - *first).length(); let minimum_span = 1.0e-6; + let in_plane_vertical = normalized( + vertical - tangent * vertical.dot(&tangent), + vertical, + ); + let vertical_change = in_plane_vertical * delta.dot(&in_plane_vertical); match grip_id { SECTION_GRIP_LEFT => { - let change = delta + let tangent_change = delta .dot(&tangent) .min((span - minimum_span).max(0.0)); - data.vertices[0] = data.vertices[0] + tangent * change; + data.vertices[0] = data.vertices[0] + tangent * tangent_change + vertical_change; } SECTION_GRIP_RIGHT => { let last = data.vertices.len() - 1; - let change = delta + let tangent_change = delta .dot(&tangent) .max((-span + minimum_span).min(0.0)); - data.vertices[last] = data.vertices[last] + tangent * change; + data.vertices[last] = + data.vertices[last] + tangent * tangent_change + vertical_change; } SECTION_GRIP_TOP => { data.top_height = (data.top_height + delta.dot(&vertical)).max(0.0); @@ -2113,6 +2133,54 @@ impl Grippable for ExtendedEntity { fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { apply_grip(self, grip_id, apply); } + + fn grip_menu(&self, grip_id: usize) -> Vec { + if grip_id != SECTION_GRIP_STATE { + return Vec::new(); + } + let ExtendedEntityData::SectionObject(data) = &self.data else { + return Vec::new(); + }; + let current = section_kind(self, data); + vec![ + GripMenuItem { + label: if current == "Plane" { "✓ Plane" } else { "Plane" }, + action: GripMenuAction::SectionPlane, + }, + GripMenuItem { + label: if current == "Slice" { "✓ Slice" } else { "Slice" }, + action: GripMenuAction::SectionSlice, + }, + GripMenuItem { + label: if current == "Boundary" { + "✓ Boundary" + } else { + "Boundary" + }, + action: GripMenuAction::SectionBoundary, + }, + GripMenuItem { + label: if current == "Volume" { "✓ Volume" } else { "Volume" }, + action: GripMenuAction::SectionVolume, + }, + ] + } + + fn apply_grip_menu(&mut self, grip_id: usize, action: GripMenuAction) { + if grip_id != SECTION_GRIP_STATE + || !matches!(self.data, ExtendedEntityData::SectionObject(_)) + { + return; + } + let value = match action { + GripMenuAction::SectionPlane => "Plane", + GripMenuAction::SectionSlice => "Slice", + GripMenuAction::SectionBoundary => "Boundary", + GripMenuAction::SectionVolume => "Volume", + _ => return, + }; + apply_section_prop(self, "ext_section_state", value); + } } impl PropertyEditable for ExtendedEntity { diff --git a/src/scene/model/object.rs b/src/scene/model/object.rs index 2d0eee684..f24423b91 100644 --- a/src/scene/model/object.rs +++ b/src/scene/model/object.rs @@ -98,6 +98,8 @@ pub enum GripShape { Circle, /// Screen-offset menu selector. Dropdown, + /// Menu selector placed immediately beside its anchor grip. + DropdownAdjacent, } /// Describes one grip point for an entity. @@ -180,6 +182,10 @@ pub enum GripMenuAction { RefineVertices, ShowFit, ShowControlVertices, + SectionPlane, + SectionSlice, + SectionBoundary, + SectionVolume, MoveWithText, StackText, UnstackText, diff --git a/src/scene/pick/grip.rs b/src/scene/pick/grip.rs index e9cdb1d25..e8ff3e72f 100644 --- a/src/scene/pick/grip.rs +++ b/src/scene/pick/grip.rs @@ -13,11 +13,18 @@ pub const GRIP_HALF_PX: f32 = 5.0; /// Screen offset for dropdown selectors anchored to a vertex. pub const GRIP_DROPDOWN_OFFSET_X_PX: f32 = 24.0; pub const GRIP_DROPDOWN_OFFSET_Y_PX: f32 = 27.0; +pub const GRIP_ADJACENT_DROPDOWN_OFFSET_X_PX: f32 = 12.0; fn marker_screen_position(mut point: Point, shape: GripShape) -> Point { - if shape == GripShape::Dropdown { - point.x += GRIP_DROPDOWN_OFFSET_X_PX; - point.y += GRIP_DROPDOWN_OFFSET_Y_PX; + match shape { + GripShape::Dropdown => { + point.x += GRIP_DROPDOWN_OFFSET_X_PX; + point.y += GRIP_DROPDOWN_OFFSET_Y_PX; + } + GripShape::DropdownAdjacent => { + point.x += GRIP_ADJACENT_DROPDOWN_OFFSET_X_PX; + } + _ => {} } point } diff --git a/src/ui/overlay.rs b/src/ui/overlay.rs index 19b7af2a4..3a47c1732 100644 --- a/src/ui/overlay.rs +++ b/src/ui/overlay.rs @@ -781,7 +781,7 @@ fn draw_grip_marker( b.close(); }), GripShape::Circle => canvas::Path::circle(Point::new(sp.x, sp.y), h), - GripShape::Dropdown => canvas::Path::new(|b| { + GripShape::Dropdown | GripShape::DropdownAdjacent => canvas::Path::new(|b| { b.move_to(Point::new(sp.x - h, sp.y - h * 0.5)); b.line_to(Point::new(sp.x + h, sp.y - h * 0.5)); b.line_to(Point::new(sp.x, sp.y + h)); @@ -831,7 +831,10 @@ fn draw_grip_marker( } else { palette.primary.base.color }; - let fill = if grip.shape == GripShape::Dropdown { + let fill = if matches!( + grip.shape, + GripShape::Dropdown | GripShape::DropdownAdjacent + ) { color } else { palette.background.base.color.scale_alpha(0.7) @@ -3797,4 +3800,3 @@ mod selection_visual_color_tests { assert!((selection_fill_alpha(100.0, true) - 0.45).abs() < 1e-5); } } -