Sketch constraints accept x / y for the sketch's own axes (#394) (#448)

`sketch.constraint.symmetry` (line), `parallel`, `perpendicular`,
`collinear`, `tangent` and `coincident` (point on curve) now take `x` /
`y` for the sketch's own axes, matching what `solid.revolve` means by
lowercase x|y. A real curve with that id still wins.

An axis becomes a linked construction line on it: a new
`LinkSource::SketchAxis` resolved in sketch coordinates, so it stays on
the sketch's axis on any plane, is shared by every constraint on that
axis, is left out of profiles and is reported in `created`. Equal,
concentric, midpoint and horizontal/vertical keep refusing axes, since
the axis line's finite length or fixed direction would make them
meaningless.


Claude-Session: https://claude.ai/code/session_01YDDPmNdRoBgkHcLeUDKVfb

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Irakli KourtisandClaude Opus 5.5 authored and GitHub committed 2026-10-12 20:31:35 -04:00
1 parent 722bd007c8
commit 0dee9deec1
5 files changed
+332 -17

No files matched your search

+1 -1
View File
@@ -42,7 +42,7 @@ pub use curves::{
pub use decompose::Budget;
pub use dims::{DimFrame, DimLayout, chain_centres, default_text, dim_frame, dim_layout, encode_text};
pub use freeform_offset::{FreeformOffset, OFFSET_TOLERANCE};
pub use link::{Link, LinkGeom, LinkKind, LinkSource, MAX_LINK_ENTITIES};
pub use link::{Link, LinkGeom, LinkKind, LinkSource, MAX_LINK_ENTITIES, SKETCH_AXIS_HALF, sketch_axis_geom};
pub use model::{Constraint, ConstraintKind, Curve, CurveKind, MAX_CONSTRAINTS, MAX_CURVES, MAX_POINTS, SPoint, Sketch, SketchError, SketchView};
pub use offset::OffsetChain;
pub use offset_topology::{OffsetCreated, OffsetRole, OffsetRoleFlags, OffsetRoleKind, OffsetTopology};
+45
View File
@@ -55,6 +55,9 @@ pub enum LinkSource {
Origin,
/// An origin axis (`X`, `Y`, `Z`).
Axis { name: String },
/// One of the sketch's own axes (`x` or `y`): the line through the sketch origin along it,
/// whatever plane the sketch is on (see [`sketch_axis_geom`]).
SketchAxis { name: String },
/// An origin plane (`XY`, `XZ`, `YZ`) or a construction plane (by name): its trace on the
/// sketch plane.
Plane { name: String },
@@ -90,6 +93,7 @@ impl LinkSource {
LinkSource::SketchPoint { point, .. } => format!("sketch point {point}"),
LinkSource::Origin => "origin".into(),
LinkSource::Axis { name } => format!("{name} axis"),
LinkSource::SketchAxis { name } => format!("sketch {name} axis"),
LinkSource::Plane { name } => format!("{name} plane"),
LinkSource::OnSurface { curve, .. } => format!("{curve} on a face"),
LinkSource::Intersection { a, b } => format!("intersection of {} and {}", a.describe(), b.describe()),
@@ -100,6 +104,19 @@ impl LinkSource {
}
}
/// Half length of the line a sketch axis link produces.
pub const SKETCH_AXIS_HALF: f64 = 100.0;
/// The geometry of sketch axis `name` (`x` or `y`, sketch coordinates); `None` for any other name.
pub fn sketch_axis_geom(name: &str) -> Option<LinkGeom> {
let u = match name {
"x" => Vec2::X,
"y" => Vec2::Y,
_ => return None,
};
Some(LinkGeom::Line(Vec2::ZERO - u * SKETCH_AXIS_HALF, u * SKETCH_AXIS_HALF))
}
/// One link and its state.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Link {
@@ -417,6 +434,34 @@ impl Sketch {
self.curves.get(ci).and_then(|c| c.link.as_deref()).and_then(|l| self.link(l)).is_some_and(|l| l.lost)
}
/// The line of sketch axis `name` (`x` or `y`) when the sketch already has one (see
/// [`Sketch::sketch_axis_line`]).
pub fn sketch_axis_curve(&self, name: &str) -> Option<usize> {
self.links
.iter()
.filter(|l| matches!(&l.source, LinkSource::SketchAxis { name: n } if n == name))
.find_map(|l| self.link_curves(&l.id).into_iter().find(|c| matches!(self.curves.get(*c).map(|c| &c.kind), Some(CurveKind::Line { .. }))))
}
/// The line of sketch axis `name` (`x` or `y`) that constraints can refer to: a linked
/// construction line on the axis (it stays there and is left out of profiles), shared by
/// every reference to that axis and added on first use. `None` when `name` is not a sketch
/// axis.
pub fn sketch_axis_line(&mut self, name: &str) -> Result<Option<usize>> {
let Some(geom) = sketch_axis_geom(name) else { return Ok(None) };
if let Some(c) = self.sketch_axis_curve(name) {
return Ok(Some(c));
}
let id = self.add_link(LinkKind::Project, LinkSource::SketchAxis { name: name.into() }, &[geom])?;
let curves = self.link_curves(&id);
for c in &curves {
if let Some(cu) = self.curves.get_mut(*c) {
cu.construction = true;
}
}
curves.first().copied().map(Some).ok_or_else(|| SketchError::Invalid(format!("sketch {name} axis")))
}
/// Add linked geometry. Returns the link id; the new curve and point ids are
/// [`Sketch::link_curves`] / [`Sketch::link_points`] of it.
pub fn add_link(&mut self, kind: LinkKind, source: LinkSource, geom: &[LinkGeom]) -> Result<String> {