forked from 1b-agent/opencadstudio-upstream
fix(modify): keep polyline thickness through join/convert/explode
JOIN, PEDIT convert, and EXPLODE rebuild polyline-family entities from scratch and copied only common entity data, so a thickness (DXF 39) set in Properties reset to 0 after those edits. Carry the source thickness onto the rebuilt Line/LwPolyline and every exploded piece, like trim and break already do for arcs. Fixes #916
This commit is contained in:
1 parent
9096f90f4d
commit
9654986a0a
3 files changed
+158
-2
No files matched your search
@@ -168,9 +168,11 @@ fn explode_polyline2d(p: &Polyline2D) -> Vec<EntityType> {
|
||||
common,
|
||||
start: Vector3::new(p0[0], p0[1], elevation),
|
||||
end: Vector3::new(p1[0], p1[1], elevation),
|
||||
thickness: p.thickness,
|
||||
..LineEnt::new()
|
||||
}));
|
||||
} else if let Some(arc) = bulge_to_arc(p0, p1, v0.bulge, elevation, &p.common) {
|
||||
} else if let Some(arc) = bulge_to_arc(p0, p1, v0.bulge, elevation, &p.common, p.thickness)
|
||||
{
|
||||
result.push(arc);
|
||||
}
|
||||
}
|
||||
@@ -237,12 +239,13 @@ fn explode_lwpolyline(p: &LwPolyline) -> Vec<EntityType> {
|
||||
common,
|
||||
start: Vector3::new(p0[0], p0[1], elevation),
|
||||
end: Vector3::new(p1[0], p1[1], elevation),
|
||||
thickness: p.thickness,
|
||||
..LineEnt::new()
|
||||
};
|
||||
result.push(EntityType::Line(line));
|
||||
} else {
|
||||
// Arc segment from bulge
|
||||
if let Some(arc) = bulge_to_arc(p0, p1, v0.bulge, elevation, &p.common) {
|
||||
if let Some(arc) = bulge_to_arc(p0, p1, v0.bulge, elevation, &p.common, p.thickness) {
|
||||
result.push(arc);
|
||||
}
|
||||
}
|
||||
@@ -258,6 +261,7 @@ fn bulge_to_arc(
|
||||
bulge: f64,
|
||||
elevation: f64,
|
||||
common_src: &EntityCommon,
|
||||
thickness: f64,
|
||||
) -> Option<EntityType> {
|
||||
let ba = crate::entities::common::BulgeArc::from_bulge(p0, p1, bulge)?;
|
||||
|
||||
@@ -280,6 +284,7 @@ fn bulge_to_arc(
|
||||
radius: ba.radius,
|
||||
start_angle,
|
||||
end_angle,
|
||||
thickness,
|
||||
..ArcEnt::new()
|
||||
};
|
||||
Some(EntityType::Arc(arc))
|
||||
@@ -1720,4 +1725,42 @@ mod tests {
|
||||
dim_line
|
||||
);
|
||||
}
|
||||
|
||||
// EXPLODE replaces the polyline with fresh Line/Arc entities; each piece
|
||||
// must carry the source thickness instead of resetting to 0 (#916).
|
||||
#[test]
|
||||
fn explode_keeps_lwpolyline_thickness() {
|
||||
use acadrust::entities::LwVertex;
|
||||
use acadrust::types::Vector2;
|
||||
|
||||
let mut pl = LwPolyline::new();
|
||||
// One straight span + one bulged span, so the result holds a Line and
|
||||
// an Arc and both paths through the rebuild are covered.
|
||||
let mut v1 = LwVertex::new(Vector2::new(0.0, 0.0));
|
||||
v1.bulge = 1.0; // semicircle to the next vertex
|
||||
pl.vertices = vec![
|
||||
v1,
|
||||
LwVertex::new(Vector2::new(10.0, 0.0)),
|
||||
LwVertex::new(Vector2::new(20.0, 0.0)),
|
||||
];
|
||||
pl.thickness = 4.0;
|
||||
|
||||
let pieces = explode_polyline_segments(&EntityType::LwPolyline(pl));
|
||||
assert!(pieces.len() >= 2, "expected a line and an arc piece");
|
||||
for piece in &pieces {
|
||||
match piece {
|
||||
EntityType::Line(l) => assert!(
|
||||
(l.thickness - 4.0).abs() < 1e-12,
|
||||
"exploded line must keep thickness, got {}",
|
||||
l.thickness
|
||||
),
|
||||
EntityType::Arc(a) => assert!(
|
||||
(a.thickness - 4.0).abs() < 1e-12,
|
||||
"exploded arc must keep thickness, got {}",
|
||||
a.thickness
|
||||
),
|
||||
other => panic!("unexpected piece type: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,9 @@ pub fn join_entities(entities: &[(Handle, &EntityType)]) -> Option<(Vec<Handle>,
|
||||
}
|
||||
let handles: Vec<Handle> = entities.iter().map(|(h, _)| *h).collect();
|
||||
let common = entities[0].1.common().clone();
|
||||
// Thickness (DXF 39) lives on the entity, not in `common`, so the rebuilt
|
||||
// chain must carry it too or a thick source joins into a flat result (#916).
|
||||
let thickness = crate::scene::view::dispatch::entity_thickness(entities[0].1).unwrap_or(0.0);
|
||||
|
||||
let (chain, closed) = stitch(segs)?;
|
||||
|
||||
@@ -198,6 +201,7 @@ pub fn join_entities(entities: &[(Handle, &EntityType)]) -> Option<(Vec<Handle>,
|
||||
line.common.handle = Handle::NULL;
|
||||
line.start = v3(verts.first().unwrap().0);
|
||||
line.end = v3(verts.last().unwrap().0);
|
||||
line.thickness = thickness;
|
||||
return Some((handles, vec![EntityType::Line(line)]));
|
||||
}
|
||||
|
||||
@@ -216,6 +220,7 @@ pub fn join_entities(entities: &[(Handle, &EntityType)]) -> Option<(Vec<Handle>,
|
||||
pl.vertices = lw_verts;
|
||||
pl.is_closed = closed;
|
||||
pl.elevation = z0;
|
||||
pl.thickness = thickness;
|
||||
return Some((handles, vec![EntityType::LwPolyline(pl)]));
|
||||
}
|
||||
|
||||
@@ -284,6 +289,71 @@ fn stitch(mut segs: Vec<Seg>) -> Option<(Vec<Seg>, bool)> {
|
||||
Some((chain, closed))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod join_tests {
|
||||
use super::*;
|
||||
use acadrust::entities::{Line as LineEnt, LwVertex};
|
||||
use acadrust::Handle;
|
||||
|
||||
fn line(x0: f64, y0: f64, x1: f64, y1: f64, thickness: f64) -> EntityType {
|
||||
let mut l = LineEnt::new();
|
||||
l.start = Vector3::new(x0, y0, 0.0);
|
||||
l.end = Vector3::new(x1, y1, 0.0);
|
||||
l.thickness = thickness;
|
||||
EntityType::Line(l)
|
||||
}
|
||||
|
||||
fn lw_2pts(p0: (f64, f64), p1: (f64, f64), thickness: f64) -> EntityType {
|
||||
let mut pl = acadrust::entities::LwPolyline::new();
|
||||
pl.vertices = vec![
|
||||
LwVertex::new(Vector2::new(p0.0, p0.1)),
|
||||
LwVertex::new(Vector2::new(p1.0, p1.1)),
|
||||
];
|
||||
pl.thickness = thickness;
|
||||
EntityType::LwPolyline(pl)
|
||||
}
|
||||
|
||||
// JOIN rebuilds the result entity; thickness must follow the chain's first
|
||||
// entity (the same source `common` comes from), not reset to 0 (#916).
|
||||
#[test]
|
||||
fn join_keeps_source_thickness() {
|
||||
let h1 = Handle::new(1);
|
||||
let h2 = Handle::new(2);
|
||||
let e1 = lw_2pts((0.0, 0.0), (10.0, 0.0), 2.5);
|
||||
// A corner keeps the chain from collapsing back to a single Line.
|
||||
let e2 = line(10.0, 0.0, 10.0, 10.0, 0.0);
|
||||
let (removed, out) = join_entities(&[(h1, &e1), (h2, &e2)]).expect("chain joins");
|
||||
assert_eq!(removed.len(), 2);
|
||||
let Some(EntityType::LwPolyline(pl)) = out.first() else {
|
||||
panic!("expected joined lwpolyline");
|
||||
};
|
||||
assert!(
|
||||
(pl.thickness - 2.5).abs() < 1e-12,
|
||||
"joined polyline must keep source thickness, got {}",
|
||||
pl.thickness
|
||||
);
|
||||
}
|
||||
|
||||
// A collinear straight run collapses to a single Line; its thickness must
|
||||
// come from the first source entity too, not default to 0.
|
||||
#[test]
|
||||
fn collinear_join_keeps_thickness() {
|
||||
let h1 = Handle::new(1);
|
||||
let h2 = Handle::new(2);
|
||||
let e1 = line(0.0, 0.0, 5.0, 0.0, 1.25);
|
||||
let e2 = line(5.0, 0.0, 10.0, 0.0, 0.0);
|
||||
let (_, out) = join_entities(&[(h1, &e1), (h2, &e2)]).expect("chain joins");
|
||||
let Some(EntityType::Line(l)) = out.first() else {
|
||||
panic!("expected collapsed line");
|
||||
};
|
||||
assert!(
|
||||
(l.thickness - 1.25).abs() < 1e-12,
|
||||
"collapsed line must keep source thickness, got {}",
|
||||
l.thickness
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// True when every vertex lies on one straight line (within tolerance).
|
||||
fn is_collinear(verts: &[(DVec3, f64)]) -> bool {
|
||||
if verts.len() < 3 {
|
||||
|
||||
@@ -686,6 +686,7 @@ pub fn convert_to_polyline(entity: &EntityType) -> Option<EntityType> {
|
||||
match entity {
|
||||
EntityType::Line(l) => {
|
||||
pl.common = l.common.clone();
|
||||
pl.thickness = l.thickness;
|
||||
pl.vertices = vec![
|
||||
LwVertex::new(Vector2::new(l.start.x, l.start.y)),
|
||||
LwVertex::new(Vector2::new(l.end.x, l.end.y)),
|
||||
@@ -693,6 +694,7 @@ pub fn convert_to_polyline(entity: &EntityType) -> Option<EntityType> {
|
||||
}
|
||||
EntityType::Arc(a) => {
|
||||
pl.common = a.common.clone();
|
||||
pl.thickness = a.thickness;
|
||||
let (sa, ea) = (a.start_angle, a.end_angle);
|
||||
let sweep = {
|
||||
let s = (ea - sa).rem_euclid(TAU);
|
||||
@@ -720,6 +722,47 @@ pub fn convert_to_polyline(entity: &EntityType) -> Option<EntityType> {
|
||||
Some(EntityType::LwPolyline(pl))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod convert_tests {
|
||||
use super::*;
|
||||
|
||||
// PEDIT convert rebuilds the entity as a polyline; thickness must move
|
||||
// onto the new entity instead of resetting to 0 (#916).
|
||||
#[test]
|
||||
fn convert_keeps_line_thickness() {
|
||||
let mut l = acadrust::entities::Line::new();
|
||||
l.start = acadrust::types::Vector3::new(0.0, 0.0, 0.0);
|
||||
l.end = acadrust::types::Vector3::new(10.0, 0.0, 0.0);
|
||||
l.thickness = 3.5;
|
||||
let Some(EntityType::LwPolyline(pl)) = convert_to_polyline(&EntityType::Line(l)) else {
|
||||
panic!("line must convert");
|
||||
};
|
||||
assert!(
|
||||
(pl.thickness - 3.5).abs() < 1e-12,
|
||||
"converted polyline must keep source thickness, got {}",
|
||||
pl.thickness
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_keeps_arc_thickness() {
|
||||
let mut a = acadrust::entities::Arc::new();
|
||||
a.center = acadrust::types::Vector3::new(0.0, 0.0, 0.0);
|
||||
a.radius = 5.0;
|
||||
a.start_angle = 0.0;
|
||||
a.end_angle = std::f64::consts::FRAC_PI_2;
|
||||
a.thickness = -2.0;
|
||||
let Some(EntityType::LwPolyline(pl)) = convert_to_polyline(&EntityType::Arc(a)) else {
|
||||
panic!("arc must convert");
|
||||
};
|
||||
assert!(
|
||||
(pl.thickness - (-2.0)).abs() < 1e-12,
|
||||
"converted polyline must keep negative thickness, got {}",
|
||||
pl.thickness
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Curve fitting ─────────────────────────────────────────────────────────
|
||||
|
||||
fn vert_xy(v: &LwVertex) -> DVec2 {
|
||||
|
||||
Reference in new issue
Block a user