Only the direction of the picked dimension line location was used, so the text always went outside beyond the arrowhead. A location inside the circle (typed, picked or JSON `at`) now places the text there: the dimension stores it as its text position, and the dimension line runs from the text out to the arrowhead; a diameter's spans the circle, broken around the text. DIMTIH (not DIMTOH) decides whether inside text is horizontal. Outside locations keep the default placement. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f4ceea7a9
commit
1418130d45
|
|
@ -232,13 +232,14 @@ fn circle_of(s: &Session, h: Handle) -> Option<(Vec2, f64)> {
|
|||
|
||||
fn radial(s: &mut Session, p: &Value, diameter: bool) -> Result<Value> {
|
||||
let id = if diameter { "dimdiameter" } else { "dimradius" };
|
||||
let (c, pt) = if let (Some(c), Some(pt)) = (point_param(p, "center"), point_param(p, "point")) {
|
||||
(c, pt)
|
||||
let (c, pt, at) = if let (Some(c), Some(pt)) = (point_param(p, "center"), point_param(p, "point")) {
|
||||
(c, pt, None)
|
||||
} else {
|
||||
let h = targets(s, p)?.first().copied().ok_or_else(|| bad(id, "`handle` of a circle/arc, or {center, point}"))?;
|
||||
let (c, r) = circle_of(s, h).ok_or_else(|| bad(id, "object is not a circle or arc"))?;
|
||||
let at = point_param(p, "at").unwrap_or(c + Vec2::from_angle(std::f64::consts::FRAC_PI_4));
|
||||
(c, c + (at - c).normalized() * r)
|
||||
let at = point_param(p, "at");
|
||||
let dir = at.unwrap_or(c + Vec2::from_angle(std::f64::consts::FRAC_PI_4));
|
||||
(c, c + (dir - c).normalized() * r, at)
|
||||
};
|
||||
if c.near(pt, 1e-12) {
|
||||
return Err(bad(id, "`point` must differ from `center`"));
|
||||
|
|
@ -248,9 +249,25 @@ fn radial(s: &mut Session, p: &Value, diameter: bool) -> Result<Value> {
|
|||
} else {
|
||||
dim(s, DimKind::Radius, c, Vec2::ZERO, Vec2::ZERO, pt, Vec2::ZERO, "")
|
||||
};
|
||||
let k = match at {
|
||||
Some(at) => text_inside(k, c, c.dist(pt), at),
|
||||
None => k,
|
||||
};
|
||||
Ok(json!({ "handle": add_dim(s, k)?.hex() }))
|
||||
}
|
||||
|
||||
/// A radius/diameter dimension line location picked inside the circle puts the text there, on
|
||||
/// the dimension line (outside, the text keeps its default place beyond the arrowhead).
|
||||
fn text_inside(mut k: EntityKind, c: Vec2, r: f64, at: Vec2) -> EntityKind {
|
||||
if let EntityKind::Dimension(d) = &mut k
|
||||
&& at.dist(c) < r
|
||||
{
|
||||
d.text_mid = v3(at);
|
||||
d.user_text_pos = true;
|
||||
}
|
||||
k
|
||||
}
|
||||
|
||||
fn run_radius(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
radial(s, p, false)
|
||||
}
|
||||
|
|
@ -1018,11 +1035,12 @@ impl RadialM {
|
|||
}
|
||||
fn kind(&self, s: &Session, c: Vec2, r: f64, at: Vec2) -> EntityKind {
|
||||
let pt = c + (at - c).normalized() * r;
|
||||
self.text.apply(if self.diameter {
|
||||
let k = self.text.apply(if self.diameter {
|
||||
dim(s, DimKind::Diameter, c + (c - pt), Vec2::ZERO, Vec2::ZERO, pt, Vec2::ZERO, "")
|
||||
} else {
|
||||
dim(s, DimKind::Radius, c, Vec2::ZERO, Vec2::ZERO, pt, Vec2::ZERO, "")
|
||||
})
|
||||
});
|
||||
text_inside(k, c, r, at)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
//! DIMRADIUS / DIMDIAMETER: a dimension line location picked inside the circle puts the text
|
||||
//! there, on the dimension line; outside, the text stays beyond the arrowhead.
|
||||
|
||||
use cadcraft_engine::Session;
|
||||
use cadcraft_engine::doc::{Dimension, EntityKind};
|
||||
use cadcraft_engine::geom::Vec2;
|
||||
use cadcraft_engine::render::{DimGeometry, LineRole};
|
||||
use serde_json::json;
|
||||
|
||||
fn last_dim(s: &Session) -> (Dimension, DimGeometry) {
|
||||
let d = s.doc().unwrap();
|
||||
match &d.model.last().unwrap().kind {
|
||||
EntityKind::Dimension(dm) => (dm.clone(), cadcraft_engine::render::dimension_in(d, dm)),
|
||||
other => panic!("not a dimension: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a dimension line passes over `p` (on the x axis).
|
||||
fn covers(g: &DimGeometry, x: f64) -> bool {
|
||||
g.lines_of(LineRole::Dim)
|
||||
.any(|l| l.windows(2).any(|w| w[0].y.abs() < 1e-9 && w[1].y.abs() < 1e-9 && w[0].x.min(w[1].x) < x && x < w[0].x.max(w[1].x)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn radial_text_goes_inside_when_the_location_is_inside() {
|
||||
let mut s = Session::new();
|
||||
s.execute("circle", &json!({"center": [0, 0], "radius": 10})).unwrap();
|
||||
|
||||
// Typed: pick the circle, then a location inside.
|
||||
s.script("dimradius 10,0 4,0\n").unwrap();
|
||||
assert!(s.running.is_none());
|
||||
let (dm, g) = last_dim(&s);
|
||||
assert!(dm.user_text_pos);
|
||||
assert!(g.text_pos.dist(Vec2::new(4.0, 0.0)) < 1e-9, "{:?}", g.text_pos);
|
||||
// The dimension line runs from beyond the text to the arrowhead and stays inside the circle.
|
||||
assert!(covers(&g, 9.0) && !covers(&g, 4.0) && !covers(&g, 1.0));
|
||||
assert!(g.lines_of(LineRole::Dim).flatten().all(|p| p.len() <= 10.0 + 1e-9));
|
||||
|
||||
// Outside: the default placement beyond the arrowhead.
|
||||
s.script("dimradius 0,10 0,15\n").unwrap();
|
||||
assert!(s.running.is_none());
|
||||
let (dm, g) = last_dim(&s);
|
||||
assert!(!dm.user_text_pos);
|
||||
assert!(g.text_pos.y > 10.0, "{:?}", g.text_pos);
|
||||
|
||||
// JSON form, diameter: the line spans the circle, broken around the text.
|
||||
let c = s.doc().unwrap().model.iter().next().unwrap().handle.hex();
|
||||
s.execute("dimdiameter", &json!({"handle": c, "at": [-3, 0]})).unwrap();
|
||||
let (_, g) = last_dim(&s);
|
||||
assert!(g.text_pos.dist(Vec2::new(-3.0, 0.0)) < 1e-9, "{:?}", g.text_pos);
|
||||
assert!(covers(&g, -9.5) && covers(&g, 9.5) && !covers(&g, -3.0));
|
||||
}
|
||||
|
|
@ -544,19 +544,40 @@ pub fn dimension_geometry_with(d: &Dimension, st: &DimStyle, dimscale: f64, font
|
|||
let (center, r) = if radius { (a, a.dist(b)) } else { (a.mid(b), a.dist(b) / 2.0) };
|
||||
let meas = if radius { r } else { 2.0 * r };
|
||||
g.mtext = linear_text(meas, d, st, if radius { "R" } else { "%%c" });
|
||||
let (tw, _) = measure(&g.mtext);
|
||||
let (tw, tht) = measure(&g.mtext);
|
||||
let u = (b - center).normalized();
|
||||
let u = if u == Vec2::ZERO || !u.is_finite() { Vec2::X } else { u };
|
||||
let tip = center + u * r;
|
||||
let tpos = if d.user_text_pos { text_mid } else { tip + u * (asz.max(tsz) * 3.0) };
|
||||
if !radius {
|
||||
let start = center - u * r;
|
||||
g.dim(vec![start, tip]);
|
||||
end(&mut g, blk1, start, -u);
|
||||
// Text placed inside the circle (a dimension line location picked inside): the text
|
||||
// sits on the dimension line, which runs from the text out to the arrowhead (for a
|
||||
// diameter it spans the circle, broken around the text). DIMTIH turns it horizontal.
|
||||
let inside = d.user_text_pos && text_mid.dist(center) < r;
|
||||
let horizontal = if inside { st.text_inside_horizontal } else { st.text_outside_horizontal };
|
||||
if inside {
|
||||
let tr = Vec2::from_angle(if horizontal { 0.0 } else { readable(u.angle()) });
|
||||
let half = if tw > 0.0 { (tw * tr.dot(u).abs() + tht * tr.perp().dot(u).abs()) / 2.0 + gap } else { 0.0 };
|
||||
let start = if radius { center } else { center - u * r };
|
||||
let (t0, t1) = ((text_mid - start).dot(u) - half, (text_mid - start).dot(u) + half);
|
||||
if !radius {
|
||||
if t0 > 0.0 {
|
||||
g.dim(vec![start, start + u * t0]);
|
||||
}
|
||||
end(&mut g, blk1, start, -u);
|
||||
}
|
||||
if t1 < tip.dist(start) {
|
||||
g.dim(vec![start + u * t1.max(0.0), tip]);
|
||||
}
|
||||
} else {
|
||||
if !radius {
|
||||
let start = center - u * r;
|
||||
g.dim(vec![start, tip]);
|
||||
end(&mut g, blk1, start, -u);
|
||||
}
|
||||
g.dim(vec![tip, tpos]);
|
||||
}
|
||||
g.dim(vec![tip, tpos]);
|
||||
end(&mut g, blk2, tip, u);
|
||||
if st.text_outside_horizontal {
|
||||
if horizontal {
|
||||
text_angle = 0.0;
|
||||
let sx = if u.x >= 0.0 { 1.0 } else { -1.0 };
|
||||
if !d.user_text_pos {
|
||||
|
|
|
|||
Loading…
Reference in New Issue