forked from 1b-agent/opencadstudio-upstream
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 <[email protected]>
This commit is contained in:
1 parent
8ab9bdfbe1
commit
e2be8197b4
3 files changed
+50
-23
No files matched your search
+2
-1
@@ -1567,7 +1567,8 @@ impl OpenCADStudio {
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
} else {
|
||||
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.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||
}
|
||||
|
||||
@@ -52,13 +52,15 @@ enum Step {
|
||||
|
||||
pub struct StretchCommand {
|
||||
handles: Vec<Handle>,
|
||||
wire_models: Vec<WireModel>,
|
||||
step: Step,
|
||||
}
|
||||
|
||||
impl StretchCommand {
|
||||
pub fn new(handles: Vec<Handle>) -> Self {
|
||||
pub fn new(handles: Vec<Handle>, wire_models: Vec<WireModel>) -> Self {
|
||||
Self {
|
||||
handles,
|
||||
wire_models,
|
||||
step: Step::WindowCorner1,
|
||||
}
|
||||
}
|
||||
@@ -128,7 +130,7 @@ impl CadCommand for StretchCommand {
|
||||
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 {
|
||||
Step::WindowCorner2(c1) => {
|
||||
// Show crossing-window rectangle preview (dashed green)
|
||||
@@ -140,31 +142,35 @@ impl CadCommand for StretchCommand {
|
||||
[c1.x, pt.y, pt.z],
|
||||
[c1.x, c1.y, c1.z],
|
||||
];
|
||||
Some(WireModel::solid(
|
||||
vec![WireModel::solid(
|
||||
"stretch_window".into(),
|
||||
pts,
|
||||
[0.3, 1.0, 0.3, 0.7],
|
||||
false,
|
||||
))
|
||||
)]
|
||||
}
|
||||
Step::Target { base, .. } => Some(WireModel {
|
||||
name: "rubber_band".into(),
|
||||
points: vec![[base.x, base.y, base.z], [pt.x, pt.y, pt.z]],
|
||||
color: WireModel::CYAN,
|
||||
selected: false,
|
||||
pattern_length: 0.0,
|
||||
pattern: [0.0; 8],
|
||||
line_weight_px: 1.0,
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
aci: 0,
|
||||
key_vertices: vec![],
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
}),
|
||||
_ => None,
|
||||
Step::Target {
|
||||
win_min,
|
||||
win_max,
|
||||
base,
|
||||
} => {
|
||||
let delta = pt - *base;
|
||||
// Live ghost: vertices inside the crossing window follow the
|
||||
// cursor, the rest stay anchored.
|
||||
let mut out: Vec<WireModel> = self
|
||||
.wire_models
|
||||
.iter()
|
||||
.map(|w| w.stretched(*win_min, *win_max, delta))
|
||||
.collect();
|
||||
out.push(WireModel::solid(
|
||||
"rubber_band".into(),
|
||||
vec![[base.x, base.y, base.z], [pt.x, pt.y, pt.z]],
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
));
|
||||
out
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,6 +155,26 @@ impl WireModel {
|
||||
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`.
|
||||
pub fn mirrored(&self, p1: glam::Vec3, p2: glam::Vec3) -> Self {
|
||||
let ax = p2.x - p1.x;
|
||||
|
||||
Reference in new issue
Block a user