forked from 1b-agent/opencadstudio-upstream
STRETCH picks its crossing window as two corner points. After the first corner set last_point, Ortho/Polar constrained the opposite corner to an axis through it, collapsing the window to a line and making selection impossible. Add CadCommand::window_corner_pick(); STRETCH returns true for its two window steps, and the viewport skips the ortho/polar (and OTRACK) lock for that point in both the preview and commit paths — the corners are now free. Also render the window like a normal box selection instead of a bare green outline: add SelectionState::preview_box (a hit-test-free marquee), draw it with the shared draw_marquee helper (green crossing fill + stroke), and drive it from the command's new window_first_corner(). Cleared on command end. Co-Authored-By: Claude Opus 4.8 <[email protected]>
147 lines
6.3 KiB
Rust
147 lines
6.3 KiB
Rust
// Auto-split from scene/mod.rs. Pure text-move; behaviour unchanged.
|
|
use super::*;
|
|
|
|
impl Scene {
|
|
// ── Preview wire ──────────────────────────────────────────────────────
|
|
|
|
pub fn set_preview_wires(&mut self, wires: Vec<WireModel>) {
|
|
// Preview wires are an overlay appended to the cached base wire set in
|
|
// `build_primitive`; they are NOT part of the tessellation cache. So a
|
|
// preview update must NOT bump `geometry_epoch` — that would re-
|
|
// tessellate the whole model on every rubber-band frame. The overlay
|
|
// forces a GPU wire re-upload on its own (the `has_overlay` content-id
|
|
// path), and iced redraws after the message that set the preview.
|
|
self.preview_wires = wires;
|
|
}
|
|
|
|
pub fn set_preview_text(&mut self, verts: Vec<crate::scene::pipeline::text_gpu::TextVertex>) {
|
|
// Overlay glyphs — same reasoning as `set_preview_wires`: no geometry
|
|
// bump. Uploaded to a dedicated per-frame text buffer in `prepare`.
|
|
self.preview_text = verts;
|
|
}
|
|
|
|
pub fn clear_preview_wire(&mut self) {
|
|
// No geometry bump — see `set_preview_wires`. Dropping the overlay
|
|
// flips the wire content id back to the base tessellation id, which
|
|
// re-uploads the base wires (without the preview) on the next frame.
|
|
self.preview_wires = vec![];
|
|
self.preview_text = vec![];
|
|
self.interim_wire = None;
|
|
// Drop any point-picked window marquee (STRETCH) so it doesn't linger
|
|
// after the command ends before the next mouse move. (#291)
|
|
self.selection.borrow_mut().preview_box = None;
|
|
}
|
|
|
|
pub fn wire_models_for(&self, handles: &[acadrust::Handle]) -> Vec<WireModel> {
|
|
handles
|
|
.iter()
|
|
.flat_map(|h| {
|
|
match self.document.get_entity(*h) {
|
|
// Hatches carry no outline in the normal wire set, but an
|
|
// edit preview (move / copy / array / grip-drag) needs to
|
|
// show the shape following the cursor. Build a live boundary
|
|
// from the current HatchModel — `apply_grip` keeps it in
|
|
// step, so the preview tracks a dragged grip in real time.
|
|
Some(EntityType::Hatch(_)) => {
|
|
self.hatch_outline_wire(*h).into_iter().collect()
|
|
}
|
|
Some(e) => self.tessellate_one(e),
|
|
None => Vec::new(),
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
/// Split a MIRROR selection into plain ghost wires (reflected wholesale) and
|
|
/// text ghosts paired with their bounding-box centre. Lets the preview match
|
|
/// the commit for TEXT: MIRRTEXT on → true glyph mirror (full reflection, same
|
|
/// as plain geometry); MIRRTEXT off → right-reading at the mirror-symmetric
|
|
/// position (reflect the centre, translate) instead of hugging the axis.
|
|
pub fn mirror_preview_parts(
|
|
&self,
|
|
handles: &[Handle],
|
|
) -> (Vec<WireModel>, Vec<(WireModel, glam::DVec3)>) {
|
|
let mut plain: Vec<WireModel> = Vec::new();
|
|
let mut texts: Vec<(WireModel, glam::DVec3)> = Vec::new();
|
|
for h in handles {
|
|
let Some(e) = self.document.get_entity(*h) else {
|
|
continue;
|
|
};
|
|
if matches!(e, EntityType::Text(_)) {
|
|
let wires = self.tessellate_one(e);
|
|
let mut lo = [f64::INFINITY; 2];
|
|
let mut hi = [f64::NEG_INFINITY; 2];
|
|
for w in &wires {
|
|
for (i, p) in w.points.iter().enumerate() {
|
|
if !p[0].is_finite() || !p[1].is_finite() {
|
|
continue;
|
|
}
|
|
// Reconstruct f64 world from the double-single pair (the
|
|
// low residual may be absent on some wires).
|
|
let l = w.points_low.get(i).copied().unwrap_or([0.0; 3]);
|
|
let (x, y) = (p[0] as f64 + l[0] as f64, p[1] as f64 + l[1] as f64);
|
|
lo[0] = lo[0].min(x);
|
|
lo[1] = lo[1].min(y);
|
|
hi[0] = hi[0].max(x);
|
|
hi[1] = hi[1].max(y);
|
|
}
|
|
}
|
|
let center = if lo[0] <= hi[0] {
|
|
glam::DVec3::new((lo[0] + hi[0]) * 0.5, (lo[1] + hi[1]) * 0.5, 0.0)
|
|
} else {
|
|
glam::DVec3::ZERO
|
|
};
|
|
for w in wires {
|
|
texts.push((w, center));
|
|
}
|
|
} else {
|
|
plain.extend(self.tessellate_one(e));
|
|
}
|
|
}
|
|
(plain, texts)
|
|
}
|
|
|
|
/// Boundary outline wire for a hatch, reconstructed from its cached
|
|
/// `HatchModel` (offsets from `world_origin`). Used only for edit previews —
|
|
/// the normal render shows the fill, not this outline.
|
|
fn hatch_outline_wire(&self, handle: Handle) -> Option<WireModel> {
|
|
let m = self.hatches.get(&handle)?;
|
|
let (wx, wy) = (m.world_origin[0], m.world_origin[1]);
|
|
let pts: Vec<[f64; 3]> = m
|
|
.boundary
|
|
.iter()
|
|
.map(|&[x, y]| {
|
|
if x.is_finite() && y.is_finite() {
|
|
[wx + x as f64, wy + y as f64, 0.0]
|
|
} else {
|
|
[f64::NAN; 3]
|
|
}
|
|
})
|
|
.collect();
|
|
if pts.len() < 2 {
|
|
return None;
|
|
}
|
|
Some(WireModel::solid_f64(
|
|
handle.value().to_string(),
|
|
pts,
|
|
m.color,
|
|
false,
|
|
))
|
|
}
|
|
|
|
/// Build wire models for an arbitrary slice of entities (e.g. clipboard contents).
|
|
/// Entities need not be in the document — they are tessellated directly.
|
|
pub fn wires_for_entities(&self, entities: &[acadrust::EntityType]) -> Vec<WireModel> {
|
|
entities
|
|
.iter()
|
|
.flat_map(|e| self.tessellate_one(e))
|
|
.collect()
|
|
}
|
|
|
|
pub fn set_interim_wire(&mut self, w: WireModel) {
|
|
// Overlay wire — same reasoning as `set_preview_wires`: no geometry
|
|
// bump, so the model isn't re-tessellated on every interim update.
|
|
self.interim_wire = Some(w);
|
|
}
|
|
}
|