STRETCH: arcs keep their height; splines stretch; real preview

An arc moved only when its centre was in the window, so stretching a
rounded end left the arc where it was or blew it up. Its ends now decide:
both in move it, one in moves that end while the arc keeps its height
(sagitta) over the new chord. Polyline arc segments keep their height the
same way instead of their bulge, spline control and fit points in the
window move, and 3D polylines and points stretch too. Old-style polylines
were tested in X/Z.

The preview moved the tessellated points that happened to be in the
window, which bent arcs out of shape; it now draws the stretched entities
through one shared function the commit also uses, via a new
preview_entities hook, and hides the originals while it shows.

Closes #1851

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-16 22:40:02 +03:00
1 parent 519d6605f9
commit 907fa8d760
6 files changed
+372 -288

No files matched your search

+4 -196
View File
@@ -293,10 +293,6 @@ impl OpenCADStudio {
})
};
let dx = delta.x as f64;
let dy = delta.y as f64; // drawing plane is world XY
let dz = delta.z as f64;
// Dimensions whose points moved — their baked *D block is
// stale afterwards and must be dropped (see #398 / #372).
let mut stretched_dims: Vec<codec::Handle> = Vec::new();
@@ -343,198 +339,10 @@ impl OpenCADStudio {
let Some(entity) = self.tabs[i].scene.document.get_entity_mut(*handle) else {
continue;
};
let mut stretched = false;
match entity {
codec::EntityType::Line(l) => {
let s_in = in_win(l.start.x, l.start.y);
let e_in = in_win(l.end.x, l.end.y);
if s_in {
l.start.x += dx;
l.start.y += dy;
l.start.z += dz;
stretched = true;
}
if e_in {
l.end.x += dx;
l.end.y += dy;
l.end.z += dz;
stretched = true;
}
}
codec::EntityType::LwPolyline(p) => {
let Some(mut world) = crate::entities::curve::lwpolyline_world_xy(p) else {
continue;
};
for v in &mut world.vertices {
if in_win(v.location.x, v.location.y) {
v.location.x += dx;
v.location.y += dy;
stretched = true;
}
}
if stretched {
*p = world;
}
}
codec::EntityType::Polyline2D(p) => {
for v in &mut p.vertices {
if in_win(v.location.x, v.location.y) {
v.location.x += dx;
v.location.y += dy;
stretched = true;
}
}
}
codec::EntityType::Polyline(p) => {
for v in &mut p.vertices {
if in_win(v.location.x, v.location.z) {
v.location.x += dx;
v.location.z += dy;
stretched = true;
}
}
}
codec::EntityType::Arc(a) => {
if in_win(a.center.x, a.center.y) {
a.center.x += dx;
a.center.y += dy;
a.center.z += dz;
stretched = true;
}
}
codec::EntityType::Circle(c) => {
if in_win(c.center.x, c.center.y) {
c.center.x += dx;
c.center.y += dy;
c.center.z += dz;
stretched = true;
}
}
codec::EntityType::Ellipse(e) => {
if in_win(e.center.x, e.center.y) {
e.center.x += dx;
e.center.y += dy;
e.center.z += dz;
stretched = true;
}
}
codec::EntityType::Insert(ins) => {
if in_win(ins.insert_point.x, ins.insert_point.y) {
ins.insert_point.x += dx;
ins.insert_point.y += dy;
ins.insert_point.z += dz;
stretched = true;
}
}
codec::EntityType::Text(t) => {
if in_win(t.insertion_point.x, t.insertion_point.y) {
t.insertion_point.x += dx;
t.insertion_point.y += dy;
t.insertion_point.z += dz;
stretched = true;
}
}
codec::EntityType::MText(t) => {
if in_win(t.insertion_point.x, t.insertion_point.y) {
t.insertion_point.x += dx;
t.insertion_point.y += dy;
t.insertion_point.z += dz;
stretched = true;
}
}
codec::EntityType::Viewport(vp) => {
stretched = windows.iter().any(|(win_min, win_max)| {
crate::entities::viewport::stretch(vp, *win_min, *win_max, delta)
});
}
codec::EntityType::Dimension(dim) => {
use codec::entities::Dimension;
// Move every definition point that falls inside the
// window — the same points the grips expose — then
// refresh the stored measurement so the value tracks
// the stretched geometry.
let mv = |p: &mut codec::types::Vector3| {
if in_win(p.x, p.y) {
p.x += dx;
p.y += dy;
p.z += dz;
true
} else {
false
}
};
match &mut **dim {
Dimension::Linear(d) => {
stretched |= mv(&mut d.first_point);
stretched |= mv(&mut d.second_point);
stretched |= mv(&mut d.definition_point);
}
Dimension::Aligned(d) => {
stretched |= mv(&mut d.first_point);
stretched |= mv(&mut d.second_point);
stretched |= mv(&mut d.definition_point);
}
Dimension::Radius(d) => {
stretched |= mv(&mut d.angle_vertex);
stretched |= mv(&mut d.definition_point);
}
Dimension::Diameter(d) => {
stretched |= mv(&mut d.angle_vertex);
stretched |= mv(&mut d.definition_point);
}
Dimension::Angular2Ln(d) => {
stretched |= mv(&mut d.angle_vertex);
stretched |= mv(&mut d.first_point);
stretched |= mv(&mut d.second_point);
stretched |= mv(&mut d.definition_point);
}
Dimension::Angular3Pt(d) => {
stretched |= mv(&mut d.angle_vertex);
stretched |= mv(&mut d.first_point);
stretched |= mv(&mut d.second_point);
stretched |= mv(&mut d.definition_point);
}
Dimension::Ordinate(d) => {
stretched |= mv(&mut d.feature_location);
stretched |= mv(&mut d.leader_endpoint);
}
Dimension::Arc(d) => {
stretched |= mv(&mut d.definition_point);
stretched |= mv(&mut d.first_extension_point);
stretched |= mv(&mut d.second_extension_point);
stretched |= mv(&mut d.center_point);
if d.has_leader {
stretched |= mv(&mut d.first_leader_point);
stretched |= mv(&mut d.second_leader_point);
}
}
Dimension::LargeRadial(d) => {
stretched |= mv(&mut d.definition_point);
stretched |= mv(&mut d.chord_point);
stretched |= mv(&mut d.override_center);
stretched |= mv(&mut d.jog_point);
}
}
// Pinned text follows too; the zero sentinel means
// "auto placement" and must not be captured by a
// window that happens to cover the origin.
let t = dim.base().text_middle_point;
if t.x * t.x + t.y * t.y + t.z * t.z > 1e-16 {
let mut t = t;
if mv(&mut t) {
dim.base_mut().text_middle_point = t;
stretched = true;
}
}
if stretched {
dim.base_mut().actual_measurement = dim.measurement();
stretched_dims.push(*handle);
}
}
_ => {
// Generic: move entire entity (treat as block-level)
stretched = false; // skip generic types
}
let stretched =
crate::modules::draw::modify::stretch::stretch_entity(entity, &windows, delta);
if stretched && matches!(entity, codec::EntityType::Dimension(_)) {
stretched_dims.push(*handle);
}
if stretched {
if let Some(before) = before {