Sketch dimensions drawn as dimensions: extension lines, dimension lines and arcs with arrowheads (flipped outside when tight), the value centred on the line (fx: for expressions), R/Ø leaders, angle arcs, arc-length mark, driven ones in parentheses in a muted colour; click selects, Delete removes, drag moves the text (sketch.dimension_text, kept relative to the dimension), double-click edits the value in place with parameter completion
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
4adfd69d6a
commit
187ffd0a03
12 files changed
+946
-57
No files matched your search
@@ -0,0 +1,369 @@
|
||||
//! Where a sketch dimension is drawn: extension lines from the measured geometry, the dimension
|
||||
//! line (or arc) with its arrows, and the value text. Shared by the UI (drawing and hit testing)
|
||||
//! and the engine (storing a dragged text position), all in sketch coordinates.
|
||||
//!
|
||||
//! A dimension's text position is kept relative to its own frame ([`Constraint::text`]), so it
|
||||
//! follows the geometry: for a linear dimension it is (along, across) from the middle of the
|
||||
//! measured points, for the others an offset from the centre or the vertex.
|
||||
//!
|
||||
//! [`Constraint::text`]: crate::Constraint::text
|
||||
|
||||
use solvecraft_geom::Vec2;
|
||||
|
||||
use crate::model::{ConstraintKind, CurveKind, Sketch};
|
||||
|
||||
/// What a dimension measures, in a form that can be drawn.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum DimFrame {
|
||||
/// The distance from `p0` to `p1` along the unit direction `dir` (aligned, horizontal or
|
||||
/// vertical).
|
||||
Linear { p0: Vec2, p1: Vec2, dir: Vec2 },
|
||||
/// Radius or diameter of a circle or arc.
|
||||
Radial { center: Vec2, radius: f64, diameter: bool },
|
||||
/// The angle at `vertex` from the unit ray `d0`, counter-clockwise by `sweep` radians.
|
||||
Angular { vertex: Vec2, d0: Vec2, sweep: f64 },
|
||||
/// Length along an arc around `center` from angle `start` by `sweep`.
|
||||
ArcLength { center: Vec2, radius: f64, start: f64, sweep: f64 },
|
||||
}
|
||||
|
||||
/// A drawn dimension (sketch coordinates).
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct DimLayout {
|
||||
/// Extension lines, dimension lines and arcs as polylines.
|
||||
pub lines: Vec<Vec<Vec2>>,
|
||||
/// Arrowheads: tip and the unit direction they point in.
|
||||
pub arrows: Vec<(Vec2, Vec2)>,
|
||||
/// Centre of the value text.
|
||||
pub text: Vec2,
|
||||
}
|
||||
|
||||
fn line_ends(sk: &Sketch, l: usize) -> Option<(Vec2, Vec2)> {
|
||||
match sk.curves.get(l)?.kind {
|
||||
CurveKind::Line { a, b } => Some((sk.point(a)?, sk.point(b)?)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The frame of a dimension constraint (None for geometric constraints).
|
||||
pub fn dim_frame(sk: &Sketch, k: &ConstraintKind) -> Option<DimFrame> {
|
||||
use ConstraintKind::*;
|
||||
let unit = |v: Vec2, or: Vec2| v.normalized().unwrap_or(or);
|
||||
Some(match *k {
|
||||
Distance { p, q, .. } => {
|
||||
let (a, b) = (sk.point(p)?, sk.point(q)?);
|
||||
DimFrame::Linear { p0: a, p1: b, dir: unit(b - a, Vec2::X) }
|
||||
}
|
||||
DistanceX { p, q, .. } => DimFrame::Linear { p0: sk.point(p)?, p1: sk.point(q)?, dir: Vec2::X },
|
||||
DistanceY { p, q, .. } => DimFrame::Linear { p0: sk.point(p)?, p1: sk.point(q)?, dir: Vec2::Y },
|
||||
Length { l, .. } => {
|
||||
let (a, b) = line_ends(sk, l)?;
|
||||
DimFrame::Linear { p0: a, p1: b, dir: unit(b - a, Vec2::X) }
|
||||
}
|
||||
PointLineDistance { p, l, .. } | LinearDiameter { p, l, .. } => {
|
||||
let (a, b) = line_ends(sk, l)?;
|
||||
let q = sk.point(p)?;
|
||||
let d = unit(b - a, Vec2::X);
|
||||
let foot = a + d * (q - a).dot(d);
|
||||
let p0 = if matches!(k, LinearDiameter { .. }) { foot * 2.0 - q } else { foot };
|
||||
DimFrame::Linear { p0, p1: q, dir: unit(q - foot, d.perp()) }
|
||||
}
|
||||
Radius { c, .. } | Diameter { c, .. } => {
|
||||
let radius = match sk.curves.get(c)?.kind {
|
||||
CurveKind::Ellipse { r, .. } => r,
|
||||
_ => sk.radius(c)?,
|
||||
};
|
||||
DimFrame::Radial { center: sk.center(c)?, radius, diameter: matches!(k, Diameter { .. }) }
|
||||
}
|
||||
ArcLength { c, .. } => match sk.segs(c).first()? {
|
||||
solvecraft_geom::Seg2::Arc { center, radius, start, sweep } => {
|
||||
DimFrame::ArcLength { center: *center, radius: *radius, start: *start, sweep: *sweep }
|
||||
}
|
||||
_ => return None,
|
||||
},
|
||||
Angle { a, b, flip, .. } => {
|
||||
let (a0, a1) = line_ends(sk, a)?;
|
||||
let (b0, b1) = line_ends(sk, b)?;
|
||||
let (da, mut db) = (unit(a1 - a0, Vec2::X), unit(b1 - b0, Vec2::Y));
|
||||
if flip {
|
||||
db = -db;
|
||||
}
|
||||
let den = da.cross(db);
|
||||
let (ma, mb) = ((a0 + a1) * 0.5, (b0 + b1) * 0.5);
|
||||
let vertex = if den.abs() < 1e-12 { (ma + mb) * 0.5 } else { a0 + da * ((b0 - a0).cross(db) / den) };
|
||||
// The angle from `da` to `db` equals the one from `-da` to `-db`: draw the pair of
|
||||
// rays that point at the lines themselves.
|
||||
let score = |s: f64| (ma - vertex).dot(da * s) + (mb - vertex).dot(db * s);
|
||||
let s = if score(1.0) >= score(-1.0) { 1.0 } else { -1.0 };
|
||||
let (d0, d1) = (da * s, db * s);
|
||||
DimFrame::Angular { vertex, d0, sweep: d0.cross(d1).atan2(d0.dot(d1)).rem_euclid(std::f64::consts::TAU) }
|
||||
}
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The stored text position for text centred at `at`.
|
||||
pub fn encode_text(f: &DimFrame, at: Vec2) -> Vec2 {
|
||||
match *f {
|
||||
DimFrame::Linear { p0, p1, dir } => {
|
||||
let m = (p0 + p1) * 0.5;
|
||||
Vec2::new((at - m).dot(dir), (at - m).dot(dir.perp()))
|
||||
}
|
||||
DimFrame::Radial { center, .. } | DimFrame::ArcLength { center, .. } => at - center,
|
||||
DimFrame::Angular { vertex, .. } => at - vertex,
|
||||
}
|
||||
}
|
||||
|
||||
/// Per point: the centre of the connected geometry it belongs to (points joined by curves),
|
||||
/// which a dimension's default place keeps away from.
|
||||
pub fn chain_centres(sk: &Sketch) -> Vec<Vec2> {
|
||||
let n = sk.points.len();
|
||||
let mut parent: Vec<usize> = (0..n).collect();
|
||||
fn root(p: &mut [usize], mut i: usize) -> usize {
|
||||
while let Some(&q) = p.get(i) {
|
||||
if q == i {
|
||||
break;
|
||||
}
|
||||
let g = p.get(q).copied().unwrap_or(q);
|
||||
if let Some(s) = p.get_mut(i) {
|
||||
*s = g;
|
||||
}
|
||||
i = q;
|
||||
}
|
||||
i
|
||||
}
|
||||
for c in &sk.curves {
|
||||
let ids = c.kind.point_ids();
|
||||
for w in ids.windows(2) {
|
||||
let (a, b) = (root(&mut parent, w[0]), root(&mut parent, w[1]));
|
||||
if let Some(s) = parent.get_mut(a) {
|
||||
*s = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut sum = vec![(Vec2::ZERO, 0usize); n];
|
||||
let roots: Vec<usize> = (0..n).map(|i| root(&mut parent, i)).collect();
|
||||
for (i, r) in roots.iter().enumerate() {
|
||||
if let (Some(s), Some(p)) = (sum.get_mut(*r), sk.points.get(i)) {
|
||||
s.0 += p.pos;
|
||||
s.1 += 1;
|
||||
}
|
||||
}
|
||||
roots.iter().map(|r| sum.get(*r).map(|(s, k)| *s / (*k).max(1) as f64).unwrap_or_default()).collect()
|
||||
}
|
||||
|
||||
/// The default text place of a dimension: a linear one on the side of its points away from
|
||||
/// `away` (the centre of the geometry it measures), the others where [`dim_layout`] puts them.
|
||||
pub fn default_text(f: &DimFrame, away: Vec2, px: f64) -> Option<Vec2> {
|
||||
match *f {
|
||||
DimFrame::Linear { p0, p1, dir } => {
|
||||
let side = if ((p0 + p1) * 0.5 - away).dot(dir.perp()) < -1e-9 { -1.0 } else { 1.0 };
|
||||
Some(Vec2::new(0.0, side * 22.0 * px))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Points along an arc around `c` (counter-clockwise from `a0` by `sweep`).
|
||||
fn arc_pts(c: Vec2, r: f64, a0: f64, sweep: f64) -> Vec<Vec2> {
|
||||
let n = ((sweep.abs() / 0.08).ceil() as usize).clamp(2, 200);
|
||||
(0..=n).map(|i| c + Vec2::from_angle(a0 + sweep * i as f64 / n as f64) * r).collect()
|
||||
}
|
||||
|
||||
/// Lay a dimension out. `text` is the stored text position (None: the default place), `px`
|
||||
/// the sketch length of one screen pixel and `half` the half size of the text box (sketch
|
||||
/// units), which the dimension line leaves a gap for.
|
||||
pub fn dim_layout(f: &DimFrame, text: Option<Vec2>, px: f64, half: Vec2) -> DimLayout {
|
||||
let px = if px.is_finite() && px > 0.0 { px } else { 1.0 };
|
||||
let arrow = 9.0 * px;
|
||||
let gap = 3.0 * px;
|
||||
let over = 4.0 * px;
|
||||
let mut out = DimLayout::default();
|
||||
match *f {
|
||||
DimFrame::Linear { p0, p1, dir } => {
|
||||
let n = dir.perp();
|
||||
let m = (p0 + p1) * 0.5;
|
||||
let t = text.unwrap_or(Vec2::new(0.0, 22.0 * px));
|
||||
let (s, h) = (t.x, t.y);
|
||||
// The dimension line sits `h` across from the middle of the measured points.
|
||||
let q0 = p0 + n * (h - (p0 - m).dot(n));
|
||||
let q1 = p1 + n * (h - (p1 - m).dot(n));
|
||||
for (p, q) in [(p0, q0), (p1, q1)] {
|
||||
let v = q - p;
|
||||
if let Some(u) = v.normalized() {
|
||||
out.lines.push(vec![p + u * gap.min(v.len()), q + u * over]);
|
||||
}
|
||||
}
|
||||
out.text = m + n * h + dir * s;
|
||||
let len = (q1 - q0).dot(dir);
|
||||
let (lo, hi) = if len >= 0.0 { (q0, q1) } else { (q1, q0) };
|
||||
let span = len.abs();
|
||||
// Half the text along the line (it is drawn level on screen).
|
||||
let along = (half.x * dir.x.abs() + half.y * dir.y.abs()) + gap;
|
||||
let inside = s.abs() + along <= span * 0.5;
|
||||
if span > 2.0 * arrow + 2.0 * along && inside {
|
||||
// Arrows inside, pointing out, the line broken around the text.
|
||||
out.arrows.push((lo, -dir));
|
||||
out.arrows.push((hi, dir));
|
||||
let tc = m + n * h + dir * s;
|
||||
out.lines.push(vec![lo, tc - dir * along]);
|
||||
out.lines.push(vec![tc + dir * along, hi]);
|
||||
} else {
|
||||
// Too tight (or the text pulled out): arrows outside pointing in, short tails.
|
||||
out.arrows.push((lo, dir));
|
||||
out.arrows.push((hi, -dir));
|
||||
let tail = arrow * 1.8;
|
||||
out.lines.push(vec![lo - dir * tail, hi + dir * tail]);
|
||||
// A text outside the extension lines gets a leader along the line.
|
||||
let tc = m + n * h + dir * s;
|
||||
let reach = (tc - m - n * h).dot(dir);
|
||||
if reach.abs() > span * 0.5 + tail {
|
||||
let end = if reach > 0.0 { hi + dir * tail } else { lo - dir * tail };
|
||||
out.lines.push(vec![end, tc - dir * (along * reach.signum())]);
|
||||
}
|
||||
}
|
||||
}
|
||||
DimFrame::Radial { center, radius, diameter } => {
|
||||
let t = text.unwrap_or_else(|| Vec2::new(1.0, 1.0) * ((radius + 18.0 * px) * std::f64::consts::FRAC_1_SQRT_2));
|
||||
let u = t.normalized().unwrap_or(Vec2::new(1.0, 1.0) * std::f64::consts::FRAC_1_SQRT_2);
|
||||
let d = t.len();
|
||||
let at = center + u * radius;
|
||||
out.text = center + t;
|
||||
let rim = half.x * u.x.abs() + half.y * u.y.abs() + gap;
|
||||
let from = if diameter { center - u * radius } else { center };
|
||||
if d > radius {
|
||||
// Leader from the far side (or centre) out to the text, arrows on the rim.
|
||||
out.lines.push(vec![from, center + u * (d - rim).max(radius)]);
|
||||
out.arrows.push((at, u));
|
||||
if diameter {
|
||||
out.arrows.push((center - u * radius, -u));
|
||||
}
|
||||
} else {
|
||||
// Text inside: the line runs rim to rim (or centre to rim) around it.
|
||||
let end = center + u * radius;
|
||||
let t0 = (d - rim).max(0.0);
|
||||
let start_d = if diameter { -radius } else { 0.0 };
|
||||
if t0 > start_d {
|
||||
out.lines.push(vec![from, center + u * t0]);
|
||||
}
|
||||
if d + rim < radius {
|
||||
out.lines.push(vec![center + u * (d + rim), end]);
|
||||
}
|
||||
out.arrows.push((at, u));
|
||||
if diameter {
|
||||
out.arrows.push((center - u * radius, -u));
|
||||
}
|
||||
}
|
||||
}
|
||||
DimFrame::Angular { vertex, d0, sweep } => {
|
||||
let a0 = d0.angle();
|
||||
let mid = a0 + sweep * 0.5;
|
||||
let t = text.unwrap_or_else(|| Vec2::from_angle(mid) * (48.0 * px));
|
||||
let r = t.len().max(arrow);
|
||||
out.text = vertex + t;
|
||||
// Extension rays from the vertex out past the arc.
|
||||
for a in [a0, a0 + sweep] {
|
||||
let u = Vec2::from_angle(a);
|
||||
out.lines.push(vec![vertex + u * (r * 0.15).min(gap * 3.0), vertex + u * (r + over)]);
|
||||
}
|
||||
let arc_len = r * sweep;
|
||||
if arc_len > 2.5 * arrow {
|
||||
out.lines.push(arc_pts(vertex, r, a0, sweep));
|
||||
out.arrows.push((vertex + Vec2::from_angle(a0) * r, -Vec2::from_angle(a0).perp()));
|
||||
out.arrows.push((vertex + Vec2::from_angle(a0 + sweep) * r, Vec2::from_angle(a0 + sweep).perp()));
|
||||
} else {
|
||||
// A narrow angle: arrows outside the sector pointing in.
|
||||
let tail = 2.0 * arrow / r;
|
||||
out.lines.push(arc_pts(vertex, r, a0 - tail, sweep + 2.0 * tail));
|
||||
out.arrows.push((vertex + Vec2::from_angle(a0) * r, Vec2::from_angle(a0).perp()));
|
||||
out.arrows.push((vertex + Vec2::from_angle(a0 + sweep) * r, -Vec2::from_angle(a0 + sweep).perp()));
|
||||
}
|
||||
// Text beyond the sector: the arc reaches round to it.
|
||||
let ta = (t.angle() - a0).rem_euclid(std::f64::consts::TAU);
|
||||
if ta > sweep {
|
||||
let (from, by) =
|
||||
if ta - sweep < std::f64::consts::TAU - ta { (a0 + sweep, ta - sweep) } else { (t.angle(), std::f64::consts::TAU - ta) };
|
||||
out.lines.push(arc_pts(vertex, r, from, by));
|
||||
}
|
||||
}
|
||||
DimFrame::ArcLength { center, radius, start, sweep } => {
|
||||
let mid = start + sweep * 0.5;
|
||||
let t = text.unwrap_or_else(|| Vec2::from_angle(mid) * (radius + 20.0 * px));
|
||||
let r = t.len().max(arrow);
|
||||
out.text = center + t;
|
||||
for a in [start, start + sweep] {
|
||||
let u = Vec2::from_angle(a);
|
||||
let (from, to) = if r >= radius { (radius + gap, r + over) } else { (radius - gap, r - over) };
|
||||
out.lines.push(vec![center + u * from, center + u * to]);
|
||||
}
|
||||
out.lines.push(arc_pts(center, r, start, sweep));
|
||||
out.arrows.push((center + Vec2::from_angle(start) * r, -Vec2::from_angle(start).perp()));
|
||||
out.arrows.push((center + Vec2::from_angle(start + sweep) * r, Vec2::from_angle(start + sweep).perp()));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn close(a: Vec2, b: Vec2) -> bool {
|
||||
a.dist(b) < 1e-9
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linear_dimension_has_extension_lines_and_inward_arrows() {
|
||||
let f = DimFrame::Linear { p0: Vec2::new(0.0, 0.0), p1: Vec2::new(100.0, 0.0), dir: Vec2::X };
|
||||
let l = dim_layout(&f, Some(Vec2::new(0.0, 10.0)), 0.1, Vec2::new(2.0, 1.0));
|
||||
assert!(close(l.text, Vec2::new(50.0, 10.0)));
|
||||
assert_eq!(l.arrows.len(), 2);
|
||||
// Arrows at the dimension line ends, pointing outwards.
|
||||
assert!(close(l.arrows[0].0, Vec2::new(0.0, 10.0)) && close(l.arrows[0].1, -Vec2::X));
|
||||
assert!(close(l.arrows[1].0, Vec2::new(100.0, 10.0)) && close(l.arrows[1].1, Vec2::X));
|
||||
// Two extension lines going up from the points, then the line broken around the text.
|
||||
assert_eq!(l.lines.len(), 4);
|
||||
assert!(l.lines[0][0].y > 0.0 && l.lines[0][1].y > 10.0);
|
||||
assert!(l.lines[2][1].x < 48.0 && l.lines[3][0].x > 52.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_linear_dimension_flips_arrows_outside() {
|
||||
let f = DimFrame::Linear { p0: Vec2::new(0.0, 0.0), p1: Vec2::new(2.0, 0.0), dir: Vec2::X };
|
||||
let l = dim_layout(&f, None, 0.1, Vec2::new(2.0, 1.0));
|
||||
assert!(close(l.arrows[0].1, Vec2::X) && close(l.arrows[1].1, -Vec2::X));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_position_round_trips_through_the_frame() {
|
||||
let f = DimFrame::Linear { p0: Vec2::new(1.0, 2.0), p1: Vec2::new(31.0, 42.0), dir: Vec2::new(0.6, 0.8) };
|
||||
let at = Vec2::new(7.0, 30.0);
|
||||
let enc = encode_text(&f, at);
|
||||
assert!(close(dim_layout(&f, Some(enc), 0.1, Vec2::ZERO).text, at));
|
||||
let r = DimFrame::Radial { center: Vec2::new(5.0, 5.0), radius: 3.0, diameter: true };
|
||||
assert!(close(dim_layout(&r, Some(encode_text(&r, at)), 0.1, Vec2::ZERO).text, at));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diameter_has_arrows_on_both_sides() {
|
||||
let r = DimFrame::Radial { center: Vec2::ZERO, radius: 10.0, diameter: true };
|
||||
let l = dim_layout(&r, Some(Vec2::new(20.0, 0.0)), 0.1, Vec2::new(2.0, 1.0));
|
||||
assert_eq!(l.arrows.len(), 2);
|
||||
assert!(close(l.arrows[0].0, Vec2::new(10.0, 0.0)) && close(l.arrows[1].0, Vec2::new(-10.0, 0.0)));
|
||||
let r = DimFrame::Radial { center: Vec2::ZERO, radius: 10.0, diameter: false };
|
||||
assert_eq!(dim_layout(&r, None, 0.1, Vec2::ZERO).arrows.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn angle_frame_uses_the_rays_toward_the_lines() {
|
||||
let mut sk = Sketch::new();
|
||||
let a = sk.add_line(Vec2::new(0.0, 0.0), Vec2::new(10.0, 0.0), None, None, None).unwrap_or(0);
|
||||
let b = sk.add_line(Vec2::new(0.0, 0.0), Vec2::new(0.0, 10.0), Some(1), None, None).unwrap_or(0);
|
||||
let k = ConstraintKind::Angle { a, b, value: std::f64::consts::FRAC_PI_2, flip: false };
|
||||
let Some(DimFrame::Angular { vertex, d0, sweep }) = dim_frame(&sk, &k) else { panic!("no frame") };
|
||||
assert!(close(vertex, Vec2::ZERO) && close(d0, Vec2::X));
|
||||
assert!((sweep - std::f64::consts::FRAC_PI_2).abs() < 1e-12);
|
||||
let l = dim_layout(&DimFrame::Angular { vertex, d0, sweep }, None, 0.1, Vec2::ZERO);
|
||||
assert_eq!(l.arrows.len(), 2);
|
||||
assert!(l.text.x > 0.0 && l.text.y > 0.0);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod curves;
|
||||
mod dims;
|
||||
mod linalg;
|
||||
mod link;
|
||||
mod model;
|
||||
@@ -21,6 +22,7 @@ mod wire;
|
||||
pub use curves::{
|
||||
MAX_SPLINE_POINTS, bspline_beziers, conic_point, ellipse_point, end_curvature, fit_beziers, spline_end_tangent, spline_point, spline_polyline,
|
||||
};
|
||||
pub use dims::{DimFrame, DimLayout, chain_centres, default_text, dim_frame, dim_layout, encode_text};
|
||||
pub use link::{Link, LinkGeom, LinkKind, LinkSource, MAX_LINK_ENTITIES};
|
||||
pub use model::{Constraint, ConstraintKind, Curve, CurveKind, SPoint, Sketch, SketchError};
|
||||
pub use profiles::{Profile, find_drawn_profiles, find_profiles, merge_regions};
|
||||
|
||||
@@ -421,6 +421,10 @@ pub struct Constraint {
|
||||
/// A driven (reference) dimension: it measures, it does not constrain.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub driven: bool,
|
||||
/// For dimensions: where the value text was placed, relative to the dimension's frame
|
||||
/// (see [`crate::dim_frame`]); None for the default place.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub text: Option<Vec2>,
|
||||
}
|
||||
|
||||
/// A 2D sketch. Point 0 is always the fixed sketch origin (id `origin`).
|
||||
@@ -597,7 +601,7 @@ impl Sketch {
|
||||
}
|
||||
self.validate(&kind)?;
|
||||
let id = self.fresh("k");
|
||||
self.constraints.push(Constraint { id: id.clone(), kind, param, driven: false });
|
||||
self.constraints.push(Constraint { id: id.clone(), kind, param, driven: false, text: None });
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user