fix(solid): normalize quadrilateral edges

Accept perimeter and DXF Z point orders while keeping screen fill and interaction on the WCS-aware geometry.

Refs #617
This commit is contained in:
Hakan Seven committed 2026-08-01 20:31:28 +03:00
1 parent 6c62daf260
commit 7b50d6a5a3
5 files changed
+151 -70

No files matched your search

+51 -14
View File
@@ -2,10 +2,10 @@
//
// Command: SOLID (reachable as SO / SOLID2D — the bare SOLID verb currently
// toggles the shaded display) — pick three or four corner points and commit a
// filled triangle or quadrilateral. Points are taken in the order picked: the
// third point is the corner diagonally opposite the second, so a four-point
// solid is entered in a Z pattern (entering the corners in ring order yields
// the classic bow-tie fill). Enter after the third point commits a triangle.
// filled triangle or quadrilateral. Four points may be picked either around
// the perimeter (the natural order shown by the rubber band) or in AutoCAD's
// traditional Z pattern. The command normalizes both forms to the DXF corner
// order before committing. Enter after the third point commits a triangle.
use acadrust::entities::Solid;
use acadrust::types::Vector3;
@@ -43,6 +43,45 @@ impl Solid2dCommand {
fn v3(p: DVec3) -> Vector3 {
Vector3::new(p.x, p.y, p.z)
}
/// True when walking the four points in pick order produces a bow-tie.
/// SOLID2D currently commits into the default world-XY OCS, so use that
/// same plane when deciding whether the last two points need swapping.
fn pick_order_crosses(points: &[DVec3]) -> bool {
if points.len() != 4 {
return false;
}
let orient = |a: DVec3, b: DVec3, c: DVec3| {
(b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
};
let crosses = |a: DVec3, b: DVec3, c: DVec3, d: DVec3| {
let ab_c = orient(a, b, c);
let ab_d = orient(a, b, d);
let cd_a = orient(c, d, a);
let cd_b = orient(c, d, b);
((ab_c > 0.0 && ab_d < 0.0) || (ab_c < 0.0 && ab_d > 0.0))
&& ((cd_a > 0.0 && cd_b < 0.0) || (cd_a < 0.0 && cd_b > 0.0))
};
crosses(points[0], points[1], points[2], points[3])
|| crosses(points[1], points[2], points[3], points[0])
}
/// Convert either perimeter picks (1-2-3-4) or classic SOLID Z picks
/// (1-2-4-3 around the perimeter) into the entity's DXF Z order.
fn solid_from_four_points(points: &[DVec3]) -> Solid {
let perimeter_order = !Self::pick_order_crosses(points);
let (third, fourth) = if perimeter_order {
(points[3], points[2])
} else {
(points[2], points[3])
};
Solid::new(
Self::v3(points[0]),
Self::v3(points[1]),
Self::v3(third),
Self::v3(fourth),
)
}
}
impl CadCommand for Solid2dCommand {
@@ -72,13 +111,7 @@ impl CadCommand for Solid2dCommand {
fn on_point(&mut self, pt: DVec3) -> CmdResult {
self.points.push(pt);
if self.points.len() == 4 {
let p = &self.points;
let solid = Solid::new(
Self::v3(p[0]),
Self::v3(p[1]),
Self::v3(p[2]),
Self::v3(p[3]),
);
let solid = Self::solid_from_four_points(&self.points);
CmdResult::CommitAndExit(EntityType::Solid(solid))
} else {
CmdResult::NeedPoint
@@ -105,9 +138,13 @@ impl CadCommand for Solid2dCommand {
}
// Outline the corners picked so far plus the cursor, closed back to the
// first point, as a rubber-band hint.
let mut pts: Vec<[f64; 3]> = self.points.iter().map(|p| [p.x, p.y, p.z]).collect();
pts.push([pt.x, pt.y, pt.z]);
pts.push([self.points[0].x, self.points[0].y, self.points[0].z]);
let mut preview = self.points.clone();
preview.push(pt);
if preview.len() == 4 && Self::pick_order_crosses(&preview) {
preview.swap(2, 3);
}
let mut pts: Vec<[f64; 3]> = preview.iter().map(|p| [p.x, p.y, p.z]).collect();
pts.push([preview[0].x, preview[0].y, preview[0].z]);
Some(WireModel::solid_f64(
"rubber_band".to_string(),
pts,