fix(section): add state grip menu

This commit is contained in:
ramox81 committed 2026-09-10 11:25:33 +03:00
1 parent 78bbd1065a
commit 0426df79ca
5 files changed
+104 -17

No files matched your search

+5 -1
View File
@@ -3049,7 +3049,11 @@ impl OpenCADStudio {
return Task::none(); return Task::none();
}; };
let grip_shape = self.tabs[i].selected_grips[grip_index].shape; 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; use crate::entities::traits::EntityTypeOps;
let items = self.tabs[i] let items = self.tabs[i]
.scene .scene
+78 -10
View File
@@ -13,7 +13,7 @@ use crate::entities::common::{
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible}; use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject}; use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
use crate::scene::model::object::{ use crate::scene::model::object::{
GripApply, GripDef, PropSection, PropValue, Property, GripApply, GripDef, GripMenuAction, GripMenuItem, PropSection, PropValue, Property,
}; };
use crate::scene::model::wire_model::SnapHint; 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_TOP: usize = 100_002;
const SECTION_GRIP_BOTTOM: usize = 100_003; const SECTION_GRIP_BOTTOM: usize = 100_003;
const SECTION_GRIP_NORMAL: usize = 100_004; const SECTION_GRIP_NORMAL: usize = 100_004;
const SECTION_GRIP_STATE: usize = 100_005;
pub(crate) fn section_is_slice(entity: &ExtendedEntity) -> bool { pub(crate) fn section_is_slice(entity: &ExtendedEntity) -> bool {
entity entity
@@ -1584,6 +1585,19 @@ fn grips(entity: &ExtendedEntity) -> Vec<GripDef> {
}) })
.collect() .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) { if let Some(center) = section_center(data) {
let normal = section_viewing_direction(data); let normal = section_viewing_direction(data);
let span = data let span = data
@@ -1789,25 +1803,25 @@ fn section_plane_edge_grips(data: &SectionObjectData) -> Vec<GripDef> {
SECTION_GRIP_LEFT, SECTION_GRIP_LEFT,
*first + vertical * edge_center_offset, *first + vertical * edge_center_offset,
vertical, vertical,
tangent, None,
), ),
( (
SECTION_GRIP_RIGHT, SECTION_GRIP_RIGHT,
*last + vertical * edge_center_offset, *last + vertical * edge_center_offset,
vertical, vertical,
tangent, None,
), ),
( (
SECTION_GRIP_TOP, SECTION_GRIP_TOP,
center + vertical * data.top_height, center + vertical * data.top_height,
tangent, tangent,
vertical, Some(vertical),
), ),
( (
SECTION_GRIP_BOTTOM, SECTION_GRIP_BOTTOM,
center - vertical * data.bottom_height, center - vertical * data.bottom_height,
tangent, tangent,
vertical, Some(vertical),
), ),
]; ];
definitions definitions
@@ -1818,7 +1832,7 @@ fn section_plane_edge_grips(data: &SectionObjectData) -> Vec<GripDef> {
is_midpoint: false, is_midpoint: false,
shape: crate::scene::model::object::GripShape::Rectangle, shape: crate::scene::model::object::GripShape::Rectangle,
dir: Some(glam::DVec3::new(edge.x, edge.y, edge.z)), 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() .collect()
} }
@@ -1850,19 +1864,25 @@ fn apply_section_plane_edge_grip(
}; };
let span = (*last - *first).length(); let span = (*last - *first).length();
let minimum_span = 1.0e-6; 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 { match grip_id {
SECTION_GRIP_LEFT => { SECTION_GRIP_LEFT => {
let change = delta let tangent_change = delta
.dot(&tangent) .dot(&tangent)
.min((span - minimum_span).max(0.0)); .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 => { SECTION_GRIP_RIGHT => {
let last = data.vertices.len() - 1; let last = data.vertices.len() - 1;
let change = delta let tangent_change = delta
.dot(&tangent) .dot(&tangent)
.max((-span + minimum_span).min(0.0)); .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 => { SECTION_GRIP_TOP => {
data.top_height = (data.top_height + delta.dot(&vertical)).max(0.0); 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) { fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
apply_grip(self, grip_id, apply); apply_grip(self, grip_id, apply);
} }
fn grip_menu(&self, grip_id: usize) -> Vec<GripMenuItem> {
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 { impl PropertyEditable for ExtendedEntity {
+6
View File
@@ -98,6 +98,8 @@ pub enum GripShape {
Circle, Circle,
/// Screen-offset menu selector. /// Screen-offset menu selector.
Dropdown, Dropdown,
/// Menu selector placed immediately beside its anchor grip.
DropdownAdjacent,
} }
/// Describes one grip point for an entity. /// Describes one grip point for an entity.
@@ -180,6 +182,10 @@ pub enum GripMenuAction {
RefineVertices, RefineVertices,
ShowFit, ShowFit,
ShowControlVertices, ShowControlVertices,
SectionPlane,
SectionSlice,
SectionBoundary,
SectionVolume,
MoveWithText, MoveWithText,
StackText, StackText,
UnstackText, UnstackText,
+10 -3
View File
@@ -13,11 +13,18 @@ pub const GRIP_HALF_PX: f32 = 5.0;
/// Screen offset for dropdown selectors anchored to a vertex. /// Screen offset for dropdown selectors anchored to a vertex.
pub const GRIP_DROPDOWN_OFFSET_X_PX: f32 = 24.0; pub const GRIP_DROPDOWN_OFFSET_X_PX: f32 = 24.0;
pub const GRIP_DROPDOWN_OFFSET_Y_PX: f32 = 27.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 { fn marker_screen_position(mut point: Point, shape: GripShape) -> Point {
if shape == GripShape::Dropdown { match shape {
point.x += GRIP_DROPDOWN_OFFSET_X_PX; GripShape::Dropdown => {
point.y += GRIP_DROPDOWN_OFFSET_Y_PX; 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 point
} }
+5 -3
View File
@@ -781,7 +781,7 @@ fn draw_grip_marker(
b.close(); b.close();
}), }),
GripShape::Circle => canvas::Path::circle(Point::new(sp.x, sp.y), h), 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.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 + h, sp.y - h * 0.5));
b.line_to(Point::new(sp.x, sp.y + h)); b.line_to(Point::new(sp.x, sp.y + h));
@@ -831,7 +831,10 @@ fn draw_grip_marker(
} else { } else {
palette.primary.base.color palette.primary.base.color
}; };
let fill = if grip.shape == GripShape::Dropdown { let fill = if matches!(
grip.shape,
GripShape::Dropdown | GripShape::DropdownAdjacent
) {
color color
} else { } else {
palette.background.base.color.scale_alpha(0.7) 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); assert!((selection_fill_alpha(100.0, true) - 0.45).abs() < 1e-5);
} }
} }