axiolid_profile/center_line.rs
1//! Centre-line profiles: an open path plus a constant width.
2
3use axiolid_core::Scalar;
4
5use crate::contour::Contour;
6
7/// An open path swept by a constant width, centred on the path.
8///
9/// This is a *representation*, not a contour: the closed area it denotes is
10/// the set of points within `half_width` of the path. Resolving it into a
11/// boundary is an offsetting algorithm, which belongs above this tier.
12///
13/// Keeping it exact matters because offsetting is lossy in a way that cannot
14/// be undone. Once a centre line is flattened into an outline, the fact that
15/// it *was* a constant-width path is gone, and with it the ability to change
16/// the width, re-offset at a different tolerance, or round-trip the source
17/// unchanged. Sheet metal, curtain-wall mullions and pipe centre lines are all
18/// authored this way.
19///
20/// The path is held as a [`Contour`] because a centre line is a sequence of
21/// bounded curve segments exactly like a boundary is; the difference is that
22/// this one is not closed and does not enclose anything on its own.
23#[derive(Debug, Clone, PartialEq)]
24#[cfg_attr(
25 feature = "serde",
26 derive(serde::Serialize, serde::Deserialize),
27 serde(deny_unknown_fields)
28)]
29pub struct CenterLineProfile {
30 /// Open path the width is measured from.
31 pub path: Contour,
32 /// Half the constant width, measured perpendicular to the path.
33 ///
34 /// Stored as a half-width rather than a full thickness so the two offset
35 /// sides are symmetric by construction: a consumer offsets by `+half` and
36 /// `-half` without having to remember to halve anything. Halving at the
37 /// boundary is the kind of factor-of-two error that produces geometry
38 /// which looks plausible and is wrong.
39 pub half_width: Scalar,
40}
41
42impl CenterLineProfile {
43 /// Construct from an open path and the full width across it.
44 pub fn from_width(path: Contour, width: Scalar) -> Self {
45 Self {
46 path,
47 half_width: width / 2.0,
48 }
49 }
50
51 /// The full width across the path.
52 pub fn width(&self) -> Scalar {
53 self.half_width * 2.0
54 }
55}