forked from 1b-agent/opencadstudio-upstream
Add dynamic input for grip-based geometry editing (#1138)
This commit is contained in:
1 parent
5ffbf598a9
commit
d47e3234d8
9 files changed
+378
-39
No files matched your search
+20
-1
@@ -365,13 +365,32 @@ impl crate::entities::traits::Grippable for Arc {
|
||||
point: glam::DVec3,
|
||||
) -> Option<f64> {
|
||||
use crate::scene::model::object::GripMenuAction as A;
|
||||
if !matches!(action, A::Lengthen) || self.radius <= 1.0e-9 {
|
||||
if self.radius <= 1.0e-9 {
|
||||
return None;
|
||||
}
|
||||
let (x, y, _) = crate::scene::view::transform::wcs_point_to_ocs(
|
||||
(point.x, point.y, point.z),
|
||||
(self.normal.x, self.normal.y, self.normal.z),
|
||||
);
|
||||
if matches!(action, A::Radius) && grip_id == 3 {
|
||||
let radius = ((x - self.center.x).powi(2) + (y - self.center.y).powi(2)).sqrt();
|
||||
return (radius > 1.0e-9).then_some(radius);
|
||||
}
|
||||
if matches!(action, A::ArcLength) && grip_id == 3 {
|
||||
let sweep = (self.end_angle - self.start_angle).rem_euclid(TAU);
|
||||
let middle_angle = self.start_angle + sweep * 0.5;
|
||||
let middle_x = self.center.x + self.radius * middle_angle.cos();
|
||||
let middle_y = self.center.y + self.radius * middle_angle.sin();
|
||||
let tangent_x = -middle_angle.sin();
|
||||
let tangent_y = middle_angle.cos();
|
||||
let delta = (x - middle_x) * tangent_x + (y - middle_y) * tangent_y;
|
||||
let length = self.radius * sweep + delta;
|
||||
return (length > 1.0e-9 && length < self.radius * TAU - 1.0e-9)
|
||||
.then_some(length);
|
||||
}
|
||||
if !matches!(action, A::Lengthen) {
|
||||
return None;
|
||||
}
|
||||
let cursor_angle = (y - self.center.y).atan2(x - self.center.x);
|
||||
let current_sweep = (self.end_angle - self.start_angle).rem_euclid(TAU);
|
||||
let desired_sweep = match grip_id {
|
||||
|
||||
+105
-2
@@ -256,7 +256,89 @@ impl RenderConvertible for Circle {
|
||||
}
|
||||
}
|
||||
|
||||
crate::impl_entity_basics!(Circle);
|
||||
impl crate::entities::traits::Grippable for Circle {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
grips(self)
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
apply_grip(self, grip_id, apply);
|
||||
}
|
||||
|
||||
fn grip_menu(
|
||||
&self,
|
||||
grip_id: usize,
|
||||
) -> Vec<crate::scene::model::object::GripMenuItem> {
|
||||
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
||||
let mut items = vec![GripMenuItem {
|
||||
label: "Stretch",
|
||||
action: GripMenuAction::Stretch,
|
||||
}];
|
||||
if (1..=4).contains(&grip_id) {
|
||||
items.push(GripMenuItem {
|
||||
label: "Radius",
|
||||
action: GripMenuAction::Radius,
|
||||
});
|
||||
}
|
||||
items
|
||||
}
|
||||
|
||||
fn grip_menu_value_prompt(
|
||||
&self,
|
||||
_grip_id: usize,
|
||||
action: crate::scene::model::object::GripMenuAction,
|
||||
) -> Option<&'static str> {
|
||||
matches!(action, crate::scene::model::object::GripMenuAction::Radius)
|
||||
.then_some("New radius")
|
||||
}
|
||||
|
||||
fn grip_menu_point_value(
|
||||
&self,
|
||||
grip_id: usize,
|
||||
action: crate::scene::model::object::GripMenuAction,
|
||||
point: glam::DVec3,
|
||||
) -> Option<f64> {
|
||||
if !matches!(action, crate::scene::model::object::GripMenuAction::Radius)
|
||||
|| !(1..=4).contains(&grip_id)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let plane = crate::entities::curve::circle_curve(self).plane;
|
||||
let point = plane.project(point.to_array())?;
|
||||
let radius = (point[0] - self.center.x).hypot(point[1] - self.center.y);
|
||||
(radius > 1.0e-9).then_some(radius)
|
||||
}
|
||||
|
||||
fn apply_grip_menu_value(
|
||||
&mut self,
|
||||
grip_id: usize,
|
||||
action: crate::scene::model::object::GripMenuAction,
|
||||
value: f64,
|
||||
) {
|
||||
if matches!(action, crate::scene::model::object::GripMenuAction::Radius)
|
||||
&& (1..=4).contains(&grip_id)
|
||||
&& value > 1.0e-9
|
||||
{
|
||||
self.radius = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::entities::traits::PropertyEditable for Circle {
|
||||
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
||||
properties(self)
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
apply_geom_prop(self, field, value);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::entities::traits::Transformable for Circle {
|
||||
fn apply_transform(&mut self, transform: &EntityTransform) {
|
||||
apply_transform(self, transform);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::entities::traits::MassPropsCalc for Circle {
|
||||
fn mass_props(&self) -> crate::entities::traits::MassProps {
|
||||
@@ -276,6 +358,28 @@ impl crate::entities::traits::MassPropsCalc for Circle {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn quadrant_grips_offer_interactive_radius() {
|
||||
use crate::entities::traits::Grippable;
|
||||
use crate::scene::model::object::GripMenuAction;
|
||||
|
||||
let mut circle = Circle::default();
|
||||
circle.radius = 2.0;
|
||||
|
||||
assert_eq!(circle.grip_menu(0).len(), 1);
|
||||
assert!(circle
|
||||
.grip_menu(1)
|
||||
.iter()
|
||||
.any(|item| item.action == GripMenuAction::Radius));
|
||||
assert_eq!(
|
||||
circle.grip_menu_value_prompt(1, GripMenuAction::Radius),
|
||||
Some("New radius")
|
||||
);
|
||||
|
||||
circle.apply_grip_menu_value(1, GripMenuAction::Radius, 5.0);
|
||||
assert_eq!(circle.radius, 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circle_segments_scales_with_zoom() {
|
||||
let r = 10.0;
|
||||
@@ -361,4 +465,3 @@ mod tests {
|
||||
assert!(close_count > far_count, "{close_count} <= {far_count}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user