forked from 1b-agent/opencadstudio-upstream
Add parallel offset grip editing for polylines
This commit is contained in:
1 parent
dd304a8424
commit
0ea220f120
7 files changed
+206
-4
No files matched your search
@@ -211,6 +211,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
| GripEditMode::ArcLength
|
| GripEditMode::ArcLength
|
||||||
| GripEditMode::RectangleWidth
|
| GripEditMode::RectangleWidth
|
||||||
| GripEditMode::RectangleHeight
|
| GripEditMode::RectangleHeight
|
||||||
|
| GripEditMode::MoveParallel
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
self.command_line.input.push_str(&s);
|
self.command_line.input.push_str(&s);
|
||||||
@@ -266,6 +267,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
| GripEditMode::ArcLength
|
| GripEditMode::ArcLength
|
||||||
| GripEditMode::RectangleWidth
|
| GripEditMode::RectangleWidth
|
||||||
| GripEditMode::RectangleHeight
|
| GripEditMode::RectangleHeight
|
||||||
|
| GripEditMode::MoveParallel
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
self.command_line.input.pop();
|
self.command_line.input.pop();
|
||||||
@@ -326,6 +328,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
crate::scene::model::object::GripMenuAction::RectangleHeight => {
|
crate::scene::model::object::GripMenuAction::RectangleHeight => {
|
||||||
Some(GripEditMode::RectangleHeight)
|
Some(GripEditMode::RectangleHeight)
|
||||||
}
|
}
|
||||||
|
crate::scene::model::object::GripMenuAction::MoveParallel => {
|
||||||
|
Some(GripEditMode::MoveParallel)
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
expected_mode.is_some_and(|mode| {
|
expected_mode.is_some_and(|mode| {
|
||||||
@@ -384,6 +389,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
) | (
|
) | (
|
||||||
GripEditMode::RectangleHeight,
|
GripEditMode::RectangleHeight,
|
||||||
crate::scene::model::object::GripMenuAction::RectangleHeight,
|
crate::scene::model::object::GripMenuAction::RectangleHeight,
|
||||||
|
) | (
|
||||||
|
GripEditMode::MoveParallel,
|
||||||
|
crate::scene::model::object::GripMenuAction::MoveParallel,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
&& grip.handle == pending.handle
|
&& grip.handle == pending.handle
|
||||||
@@ -1522,6 +1530,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
| GripMenuAction::ArcLength
|
| GripMenuAction::ArcLength
|
||||||
| GripMenuAction::RectangleWidth
|
| GripMenuAction::RectangleWidth
|
||||||
| GripMenuAction::RectangleHeight
|
| GripMenuAction::RectangleHeight
|
||||||
|
| GripMenuAction::MoveParallel
|
||||||
) {
|
) {
|
||||||
if let Some((_, grip)) = self.tabs[i]
|
if let Some((_, grip)) = self.tabs[i]
|
||||||
.selected_grip_handles
|
.selected_grip_handles
|
||||||
@@ -1561,6 +1570,11 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
popup.grip_id,
|
popup.grip_id,
|
||||||
grip.world,
|
grip.world,
|
||||||
),
|
),
|
||||||
|
GripMenuAction::MoveParallel => GripEdit::move_parallel(
|
||||||
|
popup.handle,
|
||||||
|
popup.grip_id,
|
||||||
|
grip.world,
|
||||||
|
),
|
||||||
_ => GripEdit::lengthen(
|
_ => GripEdit::lengthen(
|
||||||
popup.handle,
|
popup.handle,
|
||||||
popup.grip_id,
|
popup.grip_id,
|
||||||
@@ -1586,6 +1600,8 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
self.command_line.push_info("Specify point or enter width:");
|
self.command_line.push_info("Specify point or enter width:");
|
||||||
} else if matches!(item.action, GripMenuAction::RectangleHeight) {
|
} else if matches!(item.action, GripMenuAction::RectangleHeight) {
|
||||||
self.command_line.push_info("Specify point or enter height:");
|
self.command_line.push_info("Specify point or enter height:");
|
||||||
|
} else if matches!(item.action, GripMenuAction::MoveParallel) {
|
||||||
|
self.command_line.push_info("Specify point or enter parallel offset:");
|
||||||
} else {
|
} else {
|
||||||
self.command_line.push_info(
|
self.command_line.push_info(
|
||||||
crate::t!("Specify point or enter distance:").as_ref(),
|
crate::t!("Specify point or enter distance:").as_ref(),
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ impl OpenCADStudio {
|
|||||||
crate::scene::pick::grip::GripEditMode::RectangleHeight => {
|
crate::scene::pick::grip::GripEditMode::RectangleHeight => {
|
||||||
(grip.origin_world, Some(crate::command::DynRole::Height))
|
(grip.origin_world, Some(crate::command::DynRole::Height))
|
||||||
}
|
}
|
||||||
|
crate::scene::pick::grip::GripEditMode::MoveParallel => {
|
||||||
|
(grip.origin_world, Some(crate::command::DynRole::Distance))
|
||||||
|
}
|
||||||
crate::scene::pick::grip::GripEditMode::RectangleResize => {
|
crate::scene::pick::grip::GripEditMode::RectangleResize => {
|
||||||
(grip.origin_world, None)
|
(grip.origin_world, None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1621,6 +1621,9 @@ impl OpenCADStudio {
|
|||||||
GripEditMode::RectangleHeight => {
|
GripEditMode::RectangleHeight => {
|
||||||
Some(crate::scene::model::object::GripMenuAction::RectangleHeight)
|
Some(crate::scene::model::object::GripMenuAction::RectangleHeight)
|
||||||
}
|
}
|
||||||
|
GripEditMode::MoveParallel => {
|
||||||
|
Some(crate::scene::model::object::GripMenuAction::MoveParallel)
|
||||||
|
}
|
||||||
GripEditMode::RectangleResize => None,
|
GripEditMode::RectangleResize => None,
|
||||||
GripEditMode::Stretch => None,
|
GripEditMode::Stretch => None,
|
||||||
};
|
};
|
||||||
@@ -3332,6 +3335,7 @@ impl OpenCADStudio {
|
|||||||
grip.mode,
|
grip.mode,
|
||||||
GripEditMode::Lengthen | GripEditMode::Radius | GripEditMode::ArcLength
|
GripEditMode::Lengthen | GripEditMode::Radius | GripEditMode::ArcLength
|
||||||
| GripEditMode::RectangleWidth | GripEditMode::RectangleHeight
|
| GripEditMode::RectangleWidth | GripEditMode::RectangleHeight
|
||||||
|
| GripEditMode::MoveParallel
|
||||||
) {
|
) {
|
||||||
self.grip_pending = None;
|
self.grip_pending = None;
|
||||||
self.command_line.input.clear();
|
self.command_line.input.clear();
|
||||||
|
|||||||
@@ -996,6 +996,9 @@ bg={bg_ms:.1}ms n={view_count}"
|
|||||||
crate::scene::pick::grip::GripEditMode::RectangleHeight => {
|
crate::scene::pick::grip::GripEditMode::RectangleHeight => {
|
||||||
crate::scene::model::object::GripMenuAction::RectangleHeight
|
crate::scene::model::object::GripMenuAction::RectangleHeight
|
||||||
}
|
}
|
||||||
|
crate::scene::pick::grip::GripEditMode::MoveParallel => {
|
||||||
|
crate::scene::model::object::GripMenuAction::MoveParallel
|
||||||
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
let original = self
|
let original = self
|
||||||
@@ -1865,6 +1868,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
|||||||
| crate::scene::pick::grip::GripEditMode::ArcLength
|
| crate::scene::pick::grip::GripEditMode::ArcLength
|
||||||
| crate::scene::pick::grip::GripEditMode::RectangleWidth
|
| crate::scene::pick::grip::GripEditMode::RectangleWidth
|
||||||
| crate::scene::pick::grip::GripEditMode::RectangleHeight
|
| crate::scene::pick::grip::GripEditMode::RectangleHeight
|
||||||
|
| crate::scene::pick::grip::GripEditMode::MoveParallel
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let dyn_capturing =
|
let dyn_capturing =
|
||||||
|
|||||||
+171
-4
@@ -1162,6 +1162,73 @@ fn apply_grip(pline: &mut LwPolyline, grip_id: usize, apply: GripApply) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_segment_parallel(pline: &mut LwPolyline, seg: usize, offset: f64) {
|
||||||
|
let n = pline.vertices.len();
|
||||||
|
let count = if pline.is_closed {
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
n.saturating_sub(1)
|
||||||
|
};
|
||||||
|
if seg >= count || !offset.is_finite() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i0 = seg;
|
||||||
|
let i1 = (seg + 1) % n;
|
||||||
|
if pline.vertices[i0].bulge.abs() >= 1.0e-9 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let p0 = Vec2::new(pline.vertices[i0].location.x, pline.vertices[i0].location.y);
|
||||||
|
let p1 = Vec2::new(pline.vertices[i1].location.x, pline.vertices[i1].location.y);
|
||||||
|
let selected = p1 - p0;
|
||||||
|
let length = selected.length();
|
||||||
|
if length <= Tolerance::default().linear() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let normal = Vec2::new(-selected.y / length, selected.x / length);
|
||||||
|
let q0 = p0 + normal * offset;
|
||||||
|
|
||||||
|
let intersect = |a: Vec2, direction: Vec2| -> Option<Vec2> {
|
||||||
|
let cross = direction.x * selected.y - direction.y * selected.x;
|
||||||
|
if cross.abs() < 1.0e-9 * direction.length().max(1.0) * length.max(1.0) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let delta = q0 - a;
|
||||||
|
let t = (delta.x * selected.y - delta.y * selected.x) / cross;
|
||||||
|
Some(a + direction * t)
|
||||||
|
};
|
||||||
|
|
||||||
|
let new0 = if pline.is_closed || seg > 0 {
|
||||||
|
let previous = (i0 + n - 1) % n;
|
||||||
|
let a = Vec2::new(
|
||||||
|
pline.vertices[previous].location.x,
|
||||||
|
pline.vertices[previous].location.y,
|
||||||
|
);
|
||||||
|
let direction = p0 - a;
|
||||||
|
let Some(point) = intersect(a, direction) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
point
|
||||||
|
} else {
|
||||||
|
q0
|
||||||
|
};
|
||||||
|
let new1 = if pline.is_closed || seg + 1 < count {
|
||||||
|
let next = (i1 + 1) % n;
|
||||||
|
let d = Vec2::new(
|
||||||
|
pline.vertices[next].location.x,
|
||||||
|
pline.vertices[next].location.y,
|
||||||
|
);
|
||||||
|
let direction = d - p1;
|
||||||
|
let Some(point) = intersect(p1, direction) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
point
|
||||||
|
} else {
|
||||||
|
p1 + normal * offset
|
||||||
|
};
|
||||||
|
pline.vertices[i0].location = acadrust::types::Vector2::new(new0.x, new0.y);
|
||||||
|
pline.vertices[i1].location = acadrust::types::Vector2::new(new1.x, new1.y);
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_transform(pline: &mut LwPolyline, t: &EntityTransform) {
|
fn apply_transform(pline: &mut LwPolyline, t: &EntityTransform) {
|
||||||
crate::scene::view::transform::apply_standard_entity_transform(pline, t, |entity, p1, p2| {
|
crate::scene::view::transform::apply_standard_entity_transform(pline, t, |entity, p1, p2| {
|
||||||
for v in &mut entity.vertices {
|
for v in &mut entity.vertices {
|
||||||
@@ -1240,6 +1307,12 @@ impl crate::entities::traits::Grippable for LwPolyline {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
|
if !is_arc {
|
||||||
|
items.push(GripMenuItem {
|
||||||
|
label: "Move Parallel",
|
||||||
|
action: GripMenuAction::MoveParallel,
|
||||||
|
});
|
||||||
|
}
|
||||||
if is_rectangle(self) && seg < 4 {
|
if is_rectangle(self) && seg < 4 {
|
||||||
// Moving an edge changes the dimension perpendicular to it.
|
// Moving an edge changes the dimension perpendicular to it.
|
||||||
items.push(GripMenuItem {
|
items.push(GripMenuItem {
|
||||||
@@ -1272,6 +1345,16 @@ impl crate::entities::traits::Grippable for LwPolyline {
|
|||||||
) -> Option<&'static str> {
|
) -> Option<&'static str> {
|
||||||
use crate::scene::model::object::GripMenuAction as A;
|
use crate::scene::model::object::GripMenuAction as A;
|
||||||
let n = self.vertices.len();
|
let n = self.vertices.len();
|
||||||
|
if action == A::MoveParallel && grip_id >= n {
|
||||||
|
let seg = grip_id - n;
|
||||||
|
let count = if self.is_closed {
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
n.saturating_sub(1)
|
||||||
|
};
|
||||||
|
return (seg < count && self.vertices[seg].bulge.abs() < 1.0e-9)
|
||||||
|
.then_some("Parallel offset");
|
||||||
|
}
|
||||||
(is_rectangle(self)
|
(is_rectangle(self)
|
||||||
&& n == 4
|
&& n == 4
|
||||||
&& (n..n + 4).contains(&grip_id)
|
&& (n..n + 4).contains(&grip_id)
|
||||||
@@ -1289,6 +1372,32 @@ impl crate::entities::traits::Grippable for LwPolyline {
|
|||||||
point: glam::DVec3,
|
point: glam::DVec3,
|
||||||
) -> Option<f64> {
|
) -> Option<f64> {
|
||||||
use crate::scene::model::object::GripMenuAction as A;
|
use crate::scene::model::object::GripMenuAction as A;
|
||||||
|
if action == A::MoveParallel {
|
||||||
|
let n = self.vertices.len();
|
||||||
|
let seg = grip_id.checked_sub(n)?;
|
||||||
|
let count = if self.is_closed {
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
n.saturating_sub(1)
|
||||||
|
};
|
||||||
|
if seg >= count || self.vertices[seg].bulge.abs() >= 1.0e-9 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let plane = crate::entities::curve::lwpolyline_curve(self)?.plane;
|
||||||
|
let point = Vec2::from(plane.project(point.to_array())?);
|
||||||
|
let p0 = Vec2::new(self.vertices[seg].location.x, self.vertices[seg].location.y);
|
||||||
|
let p1 = Vec2::new(
|
||||||
|
self.vertices[(seg + 1) % n].location.x,
|
||||||
|
self.vertices[(seg + 1) % n].location.y,
|
||||||
|
);
|
||||||
|
let direction = p1 - p0;
|
||||||
|
let length = direction.length();
|
||||||
|
if length <= Tolerance::default().linear() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let normal = Vec2::new(-direction.y / length, direction.x / length);
|
||||||
|
return Some((point - p0).dot(normal));
|
||||||
|
}
|
||||||
let (frame, plane) = rectangle_frame(self)?;
|
let (frame, plane) = rectangle_frame(self)?;
|
||||||
let seg = grip_id.checked_sub(4)?;
|
let seg = grip_id.checked_sub(4)?;
|
||||||
if seg >= 4 {
|
if seg >= 4 {
|
||||||
@@ -1316,6 +1425,12 @@ impl crate::entities::traits::Grippable for LwPolyline {
|
|||||||
value: f64,
|
value: f64,
|
||||||
) {
|
) {
|
||||||
use crate::scene::model::object::GripMenuAction as A;
|
use crate::scene::model::object::GripMenuAction as A;
|
||||||
|
if action == A::MoveParallel {
|
||||||
|
if let Some(seg) = grip_id.checked_sub(self.vertices.len()) {
|
||||||
|
move_segment_parallel(self, seg, value);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if value <= Tolerance::default().linear() {
|
if value <= Tolerance::default().linear() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1563,12 +1678,14 @@ mod tests {
|
|||||||
fn rectangle_midpoint_offers_dimension_before_stretch() {
|
fn rectangle_midpoint_offers_dimension_before_stretch() {
|
||||||
let pl = make_test_rectangle();
|
let pl = make_test_rectangle();
|
||||||
let bottom = pl.grip_menu(4);
|
let bottom = pl.grip_menu(4);
|
||||||
assert_eq!(bottom[0].action, GripMenuAction::RectangleHeight);
|
assert_eq!(bottom[0].action, GripMenuAction::MoveParallel);
|
||||||
assert_eq!(bottom[1].action, GripMenuAction::Stretch);
|
assert_eq!(bottom[1].action, GripMenuAction::RectangleHeight);
|
||||||
|
assert_eq!(bottom[2].action, GripMenuAction::Stretch);
|
||||||
|
|
||||||
let right = pl.grip_menu(5);
|
let right = pl.grip_menu(5);
|
||||||
assert_eq!(right[0].action, GripMenuAction::RectangleWidth);
|
assert_eq!(right[0].action, GripMenuAction::MoveParallel);
|
||||||
assert_eq!(right[1].action, GripMenuAction::Stretch);
|
assert_eq!(right[1].action, GripMenuAction::RectangleWidth);
|
||||||
|
assert_eq!(right[2].action, GripMenuAction::Stretch);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1614,6 +1731,56 @@ mod tests {
|
|||||||
assert_eq!(value, Some(14.0));
|
assert_eq!(value, Some(14.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn polyline(points: &[(f64, f64)], closed: bool) -> LwPolyline {
|
||||||
|
let mut pl = LwPolyline::default();
|
||||||
|
pl.is_closed = closed;
|
||||||
|
pl.vertices = points
|
||||||
|
.iter()
|
||||||
|
.map(|&(x, y)| LwVertex::new(Vector2::new(x, y)))
|
||||||
|
.collect();
|
||||||
|
pl
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_parallel_reconnects_internal_edge_to_adjacent_lines() {
|
||||||
|
let mut pl = polyline(
|
||||||
|
&[(0.0, 0.0), (2.0, 2.0), (8.0, 2.0), (10.0, 0.0)],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
move_segment_parallel(&mut pl, 1, 2.0);
|
||||||
|
assert_eq!(pl.vertices[0].location, Vector2::new(0.0, 0.0));
|
||||||
|
assert_eq!(pl.vertices[1].location, Vector2::new(4.0, 4.0));
|
||||||
|
assert_eq!(pl.vertices[2].location, Vector2::new(6.0, 4.0));
|
||||||
|
assert_eq!(pl.vertices[3].location, Vector2::new(10.0, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_parallel_translates_free_endpoint_of_open_polyline() {
|
||||||
|
let mut pl = polyline(&[(0.0, 0.0), (6.0, 0.0), (8.0, 2.0)], false);
|
||||||
|
move_segment_parallel(&mut pl, 0, 2.0);
|
||||||
|
assert_eq!(pl.vertices[0].location, Vector2::new(0.0, 2.0));
|
||||||
|
assert_eq!(pl.vertices[1].location, Vector2::new(8.0, 2.0));
|
||||||
|
assert_eq!(pl.vertices[2].location, Vector2::new(8.0, 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_parallel_wraps_closed_polyline_indices() {
|
||||||
|
let mut pl = polyline(&[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)], true);
|
||||||
|
move_segment_parallel(&mut pl, 3, -1.0);
|
||||||
|
assert_eq!(pl.vertices[0].location, Vector2::new(1.0, 0.0));
|
||||||
|
assert_eq!(pl.vertices[3].location, Vector2::new(1.0, 4.0));
|
||||||
|
assert_eq!(pl.vertices[1].location, Vector2::new(4.0, 0.0));
|
||||||
|
assert_eq!(pl.vertices[2].location, Vector2::new(4.0, 4.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_parallel_rejects_parallel_adjacent_line() {
|
||||||
|
let mut pl = polyline(&[(0.0, 0.0), (2.0, 0.0), (5.0, 0.0), (6.0, 2.0)], false);
|
||||||
|
let original = pl.vertices.clone();
|
||||||
|
move_segment_parallel(&mut pl, 1, 1.0);
|
||||||
|
assert_eq!(pl.vertices, original);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lwpolyline_uniform_width_shows_global_width() {
|
fn test_lwpolyline_uniform_width_shows_global_width() {
|
||||||
let pl = make_test_lwpolyline(3, 5.0);
|
let pl = make_test_lwpolyline(3, 5.0);
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ pub enum GripMenuAction {
|
|||||||
RectangleWidth,
|
RectangleWidth,
|
||||||
RectangleHeight,
|
RectangleHeight,
|
||||||
RectangleResize,
|
RectangleResize,
|
||||||
|
MoveParallel,
|
||||||
AddVertex,
|
AddVertex,
|
||||||
RemoveVertex,
|
RemoveVertex,
|
||||||
ConvertToArc,
|
ConvertToArc,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ pub enum GripEditMode {
|
|||||||
RectangleWidth,
|
RectangleWidth,
|
||||||
RectangleHeight,
|
RectangleHeight,
|
||||||
RectangleResize,
|
RectangleResize,
|
||||||
|
MoveParallel,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -125,6 +126,12 @@ impl GripEdit {
|
|||||||
edit
|
edit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_parallel(handle: Handle, grip_id: usize, world: DVec3) -> Self {
|
||||||
|
let mut edit = Self::single(handle, grip_id, false, world);
|
||||||
|
edit.mode = GripEditMode::MoveParallel;
|
||||||
|
edit
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rectangle_resize(
|
pub fn rectangle_resize(
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
grip_id: usize,
|
grip_id: usize,
|
||||||
|
|||||||
Reference in new issue
Block a user