feat: a sketch text's height, place and angle can be expressions (#1453)
A sketch text's height, place (x and y) and angle can each be an expression over the design's parameters, and the text follows parameter changes, undo and reopening. Closes #1362. Co-authored-by: NOWN-DEV <[email protected]>
This commit is contained in:
1 parent
e20cf1858a
commit
b6806b16cf
23 files changed
+751
-70
No files matched your search
@@ -235,6 +235,9 @@ impl crate::Feature {
|
||||
mapped_name(n, parameters);
|
||||
}
|
||||
}
|
||||
for e in sketch.text_exprs_mut() {
|
||||
*e = clone_expression(e, parameters);
|
||||
}
|
||||
for link in &mut sketch.links {
|
||||
remap_link(&mut link.source, ids, names, bodies, 0);
|
||||
for n in link.face_name.iter_mut().chain(&mut link.edge_name) {
|
||||
|
||||
@@ -2792,6 +2792,7 @@ impl Document {
|
||||
}
|
||||
if let FeatureKind::Sketch { sketch, .. } = &f.kind {
|
||||
used.extend(sketch.constraints.iter().filter_map(|c| c.param.clone()));
|
||||
used.extend(sketch.text_exprs().into_iter().flat_map(|(_, e)| expr::references(e)));
|
||||
}
|
||||
}
|
||||
for p in &self.params {
|
||||
@@ -2990,6 +2991,39 @@ impl Document {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Give the texts of a sketch the numbers their expressions come to (in place): a text's
|
||||
/// height, place and angle that were typed as expressions. A text's outlines are rebuilt from
|
||||
/// its numbers when the sketch's links are refreshed, so this comes before that. A number
|
||||
/// typed as a number is left as it is.
|
||||
pub fn apply_text_values(&self, vals: &std::collections::BTreeMap<String, expr::Value>, sk: &mut Sketch) -> Result<()> {
|
||||
use crate::eval::val;
|
||||
for link in &mut sk.links {
|
||||
let solidcraft_sketch::LinkSource::Text { at, height, angle, exprs, .. } = &mut link.source else { continue };
|
||||
let number = |e: &Option<String>, kind: Kind, what: &str| -> Result<Option<f64>> {
|
||||
match e {
|
||||
None => Ok(None),
|
||||
Some(e) => val(vals, e, kind).map(Some).map_err(|error| DocError::Expr(format!("{what}: {error}"))),
|
||||
}
|
||||
};
|
||||
if let Some(v) = number(&exprs.x, Kind::Length, "text X")? {
|
||||
at.x = v;
|
||||
}
|
||||
if let Some(v) = number(&exprs.y, Kind::Length, "text Y")? {
|
||||
at.y = v;
|
||||
}
|
||||
if let Some(v) = number(&exprs.height, Kind::Length, "text height")? {
|
||||
*height = v;
|
||||
}
|
||||
if let Some(v) = number(&exprs.angle, Kind::Angle, "text angle")? {
|
||||
*angle = v;
|
||||
}
|
||||
if exprs.height.is_some() && !(*height > 1e-6 && *height < 1e5) {
|
||||
return Err(DocError::Invalid("a text's height must be positive".into()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply parameter values to a sketch's dimensions and solve it. `stored` are the values
|
||||
/// its geometry is solved for (the stored sketch's): what the solve cannot reach in one
|
||||
/// jump it reaches by stepping the changed values from there, and an edit that would turn
|
||||
|
||||
@@ -737,11 +737,13 @@ pub struct Model {
|
||||
pub no_cache: bool,
|
||||
}
|
||||
|
||||
/// Parameters a feature uses directly (inputs and sketch dimensions), sorted.
|
||||
/// Parameters a feature uses directly (inputs, sketch dimensions and the expressions of a
|
||||
/// sketch's texts), sorted.
|
||||
fn param_names(f: &Feature) -> Vec<String> {
|
||||
let mut names: Vec<String> = f.kind.expressions().iter().flat_map(|e| expr::references(e)).collect();
|
||||
if let FeatureKind::Sketch { sketch, .. } = &f.kind {
|
||||
names.extend(sketch.constraints.iter().filter_map(|c| c.param.clone()));
|
||||
names.extend(sketch.text_exprs().into_iter().flat_map(|(_, e)| expr::references(e)));
|
||||
}
|
||||
names.sort();
|
||||
names.dedup();
|
||||
@@ -1307,7 +1309,7 @@ fn point(vals: &BTreeMap<String, Value>, p: &crate::PointExpr) -> Result<Vec3> {
|
||||
if [v.x, v.y, v.z].iter().all(|c| c.is_finite() && c.abs() < 1e9) { Ok(v) } else { Err(DocError::Invalid("the position is out of range".into())) }
|
||||
}
|
||||
|
||||
fn val(vals: &BTreeMap<String, Value>, e: &str, k: Kind) -> Result<f64> {
|
||||
pub(crate) fn val(vals: &BTreeMap<String, Value>, e: &str, k: Kind) -> Result<f64> {
|
||||
let scale = vals.get(DESIGN_LEN).map(|x| x.v).unwrap_or(1.0);
|
||||
if k == Kind::Length && scale != 1.0 && !expr::is_literal(e) {
|
||||
let v = expr::eval_with(e, &|n| vals.get(n).copied().ok_or_else(|| DocError::Expr(format!("unknown parameter `{n}`"))))?;
|
||||
@@ -4082,6 +4084,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
(None, other) => (doc.resolve_plane_in(vals, other, 0, Some(&fst))?, None),
|
||||
};
|
||||
let mut sk = sketch.clone();
|
||||
// A text's outlines are rebuilt from its numbers when the links are refreshed: the
|
||||
// ones its expressions give come first.
|
||||
doc.apply_text_values(vals, &mut sk)?;
|
||||
let mut warns = match anchor {
|
||||
Some(anchor) => crate::project::refresh_face_anchor_links(doc, vals, &fst, &plane, &mut sk, anchor),
|
||||
None => crate::project::refresh_links(doc, vals, &fst, &plane, &mut sk),
|
||||
|
||||
@@ -217,6 +217,11 @@
|
||||
//! `crown` (0 by default, and then not saved: a bump that vanishes on the loop, so the face
|
||||
//! still meets the picked edges). Version 21 designs need no transformation; 0.12.0 asks for an
|
||||
//! update instead of meeting a feature it can't read or dropping a patch's crown.
|
||||
//! Also under 22, a sketch text's link source can say that its place, height or angle are
|
||||
//! expressions (`exprs`: `x`, `y`, `height`, `angle`, each an expression over the design's
|
||||
//! parameters); the source's `at`, `height` and `angle` then hold the numbers they last came
|
||||
//! to. A text without `exprs` is set as it always was; 0.12.0 asks for an update instead of
|
||||
//! dropping the expressions and keeping the text at its last numbers.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
|
||||
@@ -584,6 +584,11 @@ impl Document {
|
||||
out.push(ParamUser::Dimension { feature: f.id, name: f.name.clone(), constraint: c.id.clone() });
|
||||
}
|
||||
}
|
||||
for (what, e) in sketch.text_exprs() {
|
||||
if expr::references(e).iter().any(|r| r == name) {
|
||||
out.push(ParamUser::Feature { feature: f.id, name: f.name.clone(), input: what.to_string() });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
@@ -662,6 +667,13 @@ impl Document {
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
for e in sketch.text_exprs_mut() {
|
||||
let r = expr::rename_reference(e, old, new);
|
||||
if r != *e {
|
||||
*e = r;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.favorites.remove(old) {
|
||||
@@ -717,6 +729,7 @@ impl Document {
|
||||
let mut names: BTreeSet<String> = f.param_names.iter().filter(|n| !n.is_empty()).cloned().collect();
|
||||
if let FeatureKind::Sketch { sketch, .. } = &f.kind {
|
||||
names.extend(sketch.constraints.iter().filter_map(|c| c.param.clone()));
|
||||
names.extend(sketch.text_exprs().into_iter().flat_map(|(_, e)| expr::references(e)));
|
||||
}
|
||||
out.extend(names.into_iter().map(|n| (format!("Feature:{}", f.name), n)));
|
||||
}
|
||||
@@ -744,6 +757,7 @@ impl Document {
|
||||
hit |= f.kind.expressions().iter().any(|e| expr::references(e).iter().any(|r| affected.contains(r)));
|
||||
if let FeatureKind::Sketch { sketch, .. } = &f.kind {
|
||||
hit |= sketch.constraints.iter().any(|c| c.param.as_ref().is_some_and(|p| affected.contains(p)));
|
||||
hit |= sketch.text_exprs().iter().any(|(_, e)| expr::references(e).iter().any(|r| affected.contains(r)));
|
||||
}
|
||||
if hit {
|
||||
out.push(f.id);
|
||||
|
||||
@@ -255,7 +255,7 @@ fn resolve_uncached(
|
||||
LinkSource::SketchAxis { name } => {
|
||||
vec![solidcraft_sketch::sketch_axis_geom(name).ok_or_else(|| DocError::Unknown(format!("sketch axis `{name}`")))?]
|
||||
}
|
||||
LinkSource::Text { text, at, height, angle, font, style } if style.path.is_none() => {
|
||||
LinkSource::Text { text, at, height, angle, font, style, .. } if style.path.is_none() => {
|
||||
solidcraft_sketch::text_geometry_styled(font.as_deref(), text, *at, *height, *angle, style, None)?
|
||||
}
|
||||
// Its path is a curve of its own sketch: set with the sketch (`Sketch::text_source_geometry`).
|
||||
|
||||
Reference in new issue
Block a user