forked from 1b-agent/opencadstudio-upstream
fix(measure): space points along a curve by distance, not by parameter
DIVIDE and MEASURE did nothing at all on an ELLIPSE or a SPLINE. Their length function answered `0.0` for both — the `_ => 0.0` arm — and the command reported the entity type as unsupported. On a polyline they measured the chords and ignored the bulges, so points landed short of where they belonged; on an arc or a circle they read the centre as world coordinates when the entity stores it in its OCS. All three now walk the entity's own curve, which the kernel measures properly: closed forms where they exist, and the integral of the speed where they do not. A path ARRAY had the same confusion in a different place. It sampled the path at even *parameters*, which is the same as even distances on a line and a circle and nothing like it on an ellipse or a spline — copies bunched up wherever the parameter ran slow. It also now closes properly: on a closed path the last copy no longer lands on the first. LENGTHEN measured an ellipse with a hundred and twenty-eight chords and then bisected on that, and a spline with forty rounds of thirty-two chords apiece. A chord sum reads short, so "current length" was already below the true one before any delta was applied. Both now ask the kernel, which also makes the spline case one evaluation instead of about thirteen hundred. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e6345f89f7
commit
d32f39221c
4 files changed
+116
-403
No files matched your search
@@ -1,12 +1,12 @@
|
||||
// DIVIDE command — place Point entities at N equal intervals along an entity.
|
||||
// MEASURE command — place Point entities at fixed-distance intervals along an entity.
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use acadrust::entities::Point as PointEnt;
|
||||
use acadrust::kernel::space::PlanarCurve;
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::{EntityType, Handle};
|
||||
use glam::DVec3;
|
||||
use crate::entities::curve::entity_curve;
|
||||
use crate::t;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
@@ -164,16 +164,12 @@ pub fn divide_entity(entity: &EntityType, n: usize) -> Vec<EntityType> {
|
||||
if n < 2 {
|
||||
return vec![];
|
||||
}
|
||||
let total = entity_length(entity);
|
||||
if total < 1e-10 {
|
||||
let Some((curve, total)) = measurable(entity) else {
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
let step = total / n as f64;
|
||||
(1..n)
|
||||
.filter_map(|k| {
|
||||
let t = step * k as f64;
|
||||
point_at_distance(entity, t).map(make_point)
|
||||
})
|
||||
.map(|k| make_point(curve.point_at_distance(step * k as f64)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -182,135 +178,30 @@ pub fn measure_entity(entity: &EntityType, segment_length: f64) -> Vec<EntityTyp
|
||||
if segment_length <= 0.0 {
|
||||
return vec![];
|
||||
}
|
||||
let total = entity_length(entity);
|
||||
if total < 1e-10 {
|
||||
let Some((curve, total)) = measurable(entity) else {
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
let mut pts = Vec::new();
|
||||
let mut t = segment_length;
|
||||
while t < total - 1e-6 {
|
||||
if let Some(p) = point_at_distance(entity, t) {
|
||||
pts.push(make_point(p));
|
||||
}
|
||||
t += segment_length;
|
||||
let mut walked = segment_length;
|
||||
while walked < total - 1e-6 {
|
||||
pts.push(make_point(curve.point_at_distance(walked)));
|
||||
walked += segment_length;
|
||||
}
|
||||
pts
|
||||
}
|
||||
|
||||
fn make_point(pos: Vector3) -> EntityType {
|
||||
fn make_point(pos: [f64; 3]) -> EntityType {
|
||||
let mut p = PointEnt::new();
|
||||
p.location = pos;
|
||||
p.location = Vector3::new(pos[0], pos[1], pos[2]);
|
||||
EntityType::Point(p)
|
||||
}
|
||||
|
||||
fn entity_length(entity: &EntityType) -> f64 {
|
||||
match entity {
|
||||
EntityType::Line(l) => {
|
||||
let dx = l.end.x - l.start.x;
|
||||
let dy = l.end.y - l.start.y;
|
||||
let dz = l.end.z - l.start.z;
|
||||
(dx * dx + dy * dy + dz * dz).sqrt()
|
||||
}
|
||||
EntityType::Arc(a) => {
|
||||
let span = arc_span_rad(a.start_angle, a.end_angle);
|
||||
a.radius * span
|
||||
}
|
||||
EntityType::Circle(c) => 2.0 * PI * c.radius,
|
||||
EntityType::LwPolyline(p) => {
|
||||
let n = p.vertices.len();
|
||||
if n < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
let segs = if p.is_closed { n } else { n - 1 };
|
||||
(0..segs)
|
||||
.map(|i| {
|
||||
let v0 = &p.vertices[i];
|
||||
let v1 = &p.vertices[(i + 1) % n];
|
||||
let dx = v1.location.x - v0.location.x;
|
||||
let dy = v1.location.y - v0.location.y;
|
||||
(dx * dx + dy * dy).sqrt()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn point_at_distance(entity: &EntityType, d: f64) -> Option<Vector3> {
|
||||
match entity {
|
||||
EntityType::Line(l) => {
|
||||
let total = entity_length(entity);
|
||||
if total < 1e-10 {
|
||||
return None;
|
||||
}
|
||||
let t = (d / total).clamp(0.0, 1.0);
|
||||
Some(Vector3::new(
|
||||
l.start.x + t * (l.end.x - l.start.x),
|
||||
l.start.y + t * (l.end.y - l.start.y),
|
||||
l.start.z + t * (l.end.z - l.start.z),
|
||||
))
|
||||
}
|
||||
EntityType::Arc(a) => {
|
||||
let span = arc_span_rad(a.start_angle, a.end_angle);
|
||||
let t = d / a.radius; // arc_length = r * theta
|
||||
if t > span {
|
||||
return None;
|
||||
}
|
||||
let angle = a.start_angle + t;
|
||||
Some(Vector3::new(
|
||||
a.center.x + a.radius * angle.cos(),
|
||||
a.center.y + a.radius * angle.sin(),
|
||||
a.center.z,
|
||||
))
|
||||
}
|
||||
EntityType::Circle(c) => {
|
||||
let circumference = 2.0 * PI * c.radius;
|
||||
if circumference < 1e-10 {
|
||||
return None;
|
||||
}
|
||||
let angle = 2.0 * PI * (d / circumference);
|
||||
Some(Vector3::new(
|
||||
c.center.x + c.radius * angle.cos(),
|
||||
c.center.y + c.radius * angle.sin(),
|
||||
c.center.z,
|
||||
))
|
||||
}
|
||||
EntityType::LwPolyline(p) => {
|
||||
let n = p.vertices.len();
|
||||
if n < 2 {
|
||||
return None;
|
||||
}
|
||||
let segs = if p.is_closed { n } else { n - 1 };
|
||||
let mut acc = 0.0f64;
|
||||
for i in 0..segs {
|
||||
let v0 = &p.vertices[i];
|
||||
let v1 = &p.vertices[(i + 1) % n];
|
||||
let dx = v1.location.x - v0.location.x;
|
||||
let dy = v1.location.y - v0.location.y;
|
||||
let seg_len = (dx * dx + dy * dy).sqrt();
|
||||
if acc + seg_len >= d - 1e-10 {
|
||||
let t = (d - acc) / seg_len.max(1e-10);
|
||||
return Some(Vector3::new(
|
||||
v0.location.x + t * dx,
|
||||
v0.location.y + t * dy,
|
||||
p.elevation,
|
||||
));
|
||||
}
|
||||
acc += seg_len;
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn arc_span_rad(start: f64, end: f64) -> f64 {
|
||||
let span = (end - start).rem_euclid(2.0 * PI);
|
||||
if span < 1e-6 {
|
||||
2.0 * PI
|
||||
} else {
|
||||
span
|
||||
}
|
||||
/// The entity's curve and its length, or `None` for anything that cannot be
|
||||
/// walked along — a hatch, a block, an unbounded ray.
|
||||
fn measurable(entity: &EntityType) -> Option<(PlanarCurve, f64)> {
|
||||
let curve = entity_curve(entity)?;
|
||||
let total = curve.curve.length();
|
||||
(total.is_finite() && total > 1e-10).then_some((curve, total))
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user