feat(stretch): live ghost preview while dragging the new point
STRETCH now shows a live ghost where vertices inside the crossing window follow the cursor and the rest stay anchored, instead of only a rubber-band line. Add WireModel::stretched and pass the selection's wires into the command. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8ab9bdfbe1
commit
e2be8197b4
3 changed files with 50 additions and 23 deletions
|
|
@ -1567,7 +1567,8 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
} else {
|
} else {
|
||||||
use crate::modules::home::modify::stretch::StretchCommand;
|
use crate::modules::home::modify::stretch::StretchCommand;
|
||||||
let new_cmd = StretchCommand::new(handles);
|
let wires = self.tabs[i].scene.wire_models_for(&handles);
|
||||||
|
let new_cmd = StretchCommand::new(handles, wires);
|
||||||
self.command_line.push_info(&new_cmd.prompt());
|
self.command_line.push_info(&new_cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,13 +52,15 @@ enum Step {
|
||||||
|
|
||||||
pub struct StretchCommand {
|
pub struct StretchCommand {
|
||||||
handles: Vec<Handle>,
|
handles: Vec<Handle>,
|
||||||
|
wire_models: Vec<WireModel>,
|
||||||
step: Step,
|
step: Step,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StretchCommand {
|
impl StretchCommand {
|
||||||
pub fn new(handles: Vec<Handle>) -> Self {
|
pub fn new(handles: Vec<Handle>, wire_models: Vec<WireModel>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
handles,
|
handles,
|
||||||
|
wire_models,
|
||||||
step: Step::WindowCorner1,
|
step: Step::WindowCorner1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +130,7 @@ impl CadCommand for StretchCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mouse_move(&mut self, pt: Vec3) -> Option<WireModel> {
|
fn on_preview_wires(&mut self, pt: Vec3) -> Vec<WireModel> {
|
||||||
match &self.step {
|
match &self.step {
|
||||||
Step::WindowCorner2(c1) => {
|
Step::WindowCorner2(c1) => {
|
||||||
// Show crossing-window rectangle preview (dashed green)
|
// Show crossing-window rectangle preview (dashed green)
|
||||||
|
|
@ -140,31 +142,35 @@ impl CadCommand for StretchCommand {
|
||||||
[c1.x, pt.y, pt.z],
|
[c1.x, pt.y, pt.z],
|
||||||
[c1.x, c1.y, c1.z],
|
[c1.x, c1.y, c1.z],
|
||||||
];
|
];
|
||||||
Some(WireModel::solid(
|
vec![WireModel::solid(
|
||||||
"stretch_window".into(),
|
"stretch_window".into(),
|
||||||
pts,
|
pts,
|
||||||
[0.3, 1.0, 0.3, 0.7],
|
[0.3, 1.0, 0.3, 0.7],
|
||||||
false,
|
false,
|
||||||
))
|
)]
|
||||||
}
|
}
|
||||||
Step::Target { base, .. } => Some(WireModel {
|
Step::Target {
|
||||||
name: "rubber_band".into(),
|
win_min,
|
||||||
points: vec![[base.x, base.y, base.z], [pt.x, pt.y, pt.z]],
|
win_max,
|
||||||
color: WireModel::CYAN,
|
base,
|
||||||
selected: false,
|
} => {
|
||||||
pattern_length: 0.0,
|
let delta = pt - *base;
|
||||||
pattern: [0.0; 8],
|
// Live ghost: vertices inside the crossing window follow the
|
||||||
line_weight_px: 1.0,
|
// cursor, the rest stay anchored.
|
||||||
snap_pts: vec![],
|
let mut out: Vec<WireModel> = self
|
||||||
tangent_geoms: vec![],
|
.wire_models
|
||||||
aci: 0,
|
.iter()
|
||||||
key_vertices: vec![],
|
.map(|w| w.stretched(*win_min, *win_max, delta))
|
||||||
aabb: WireModel::UNBOUNDED_AABB,
|
.collect();
|
||||||
plinegen: true,
|
out.push(WireModel::solid(
|
||||||
vp_scissor: None,
|
"rubber_band".into(),
|
||||||
fill_tris: vec![],
|
vec![[base.x, base.y, base.z], [pt.x, pt.y, pt.z]],
|
||||||
}),
|
WireModel::CYAN,
|
||||||
_ => None,
|
false,
|
||||||
|
));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
_ => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,26 @@ impl WireModel {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a clone for a stretch preview: every point whose XY lies inside
|
||||||
|
/// the crossing window `[win_min, win_max]` is translated by `delta`; points
|
||||||
|
/// outside stay put. Exact for line/polyline vertices (the primary stretch
|
||||||
|
/// targets); curve tessellation points may deform where a window edge cuts
|
||||||
|
/// through them, matching the per-vertex nature of the operation.
|
||||||
|
pub fn stretched(&self, win_min: glam::Vec3, win_max: glam::Vec3, delta: glam::Vec3) -> Self {
|
||||||
|
let mut out = self.clone();
|
||||||
|
out.name = format!("preview_{}", self.name);
|
||||||
|
out.color = Self::CYAN;
|
||||||
|
out.selected = false;
|
||||||
|
for p in &mut out.points {
|
||||||
|
if p[0] >= win_min.x && p[0] <= win_max.x && p[1] >= win_min.y && p[1] <= win_max.y {
|
||||||
|
p[0] += delta.x;
|
||||||
|
p[1] += delta.y;
|
||||||
|
p[2] += delta.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a clone mirrored across the line through `p1`→`p2`.
|
/// Return a clone mirrored across the line through `p1`→`p2`.
|
||||||
pub fn mirrored(&self, p1: glam::Vec3, p2: glam::Vec3) -> Self {
|
pub fn mirrored(&self, p1: glam::Vec3, p2: glam::Vec3) -> Self {
|
||||||
let ax = p2.x - p1.x;
|
let ax = p2.x - p1.x;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue