feat: edge flanges with their own angle and height per edge (#447)
One edge flange feature gave all its flanges one height and one angle. A
part whose flanges lean differently took one feature per angle, and
flanges of separate features don't close toward each other, so its
corners stayed open.
- sheet.flange (edge): an entry of `edges` is a point [x, y, z] as before,
or {point, angle?, height?} whose angle and height (expressions, checked
like the feature's) replace the feature's for that edge. Bad input is
refused with the edge named. Before, the object form was read for its
point and the rest was ignored without a word.
- The feature stores its edges as FlangeEdge. An edge without values of
its own is written as the plain point it always was, so designs without
them are read and written as before; an unknown key in an edge is
refused, not dropped.
- The edges' values are feature inputs of their own (Edge angle, Edge
height, after the feature's), so parameters, renames and
parameters.change reach them. timeline.edit names the edges again when
it changes them, so each value stays with its edge.
- SheetBody::add_flanges_each trims every edge by its own setback and
adds the flanges. The corners between them close as between equal
flanges: the closing already works from the two flanges it joins (each
one's fold and leg), so the flat pattern follows by hand from the
angles, the heights and the rule (the tests derive it).
- The Flange dialog shows a row per picked edge with an angle and a
height box (empty: the dialog's value) once more than one edge is
picked, and brings them back on Edit Feature.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0dee9deec1
commit
fb4c1d74b9
14 files changed
+1039
-38
No files matched your search
@@ -269,6 +269,73 @@ impl EmbossFace {
|
||||
}
|
||||
}
|
||||
|
||||
/// One edge of an edge flange feature: a point on the sheet's edge and, where given, this
|
||||
/// edge's own angle and height (expressions) in place of the feature's. In a design file an
|
||||
/// edge with neither is the point `[x, y, z]`, as every edge was before; one with its own
|
||||
/// values is `{"point": [x, y, z], "angle": "…", "height": "…"}`.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct FlangeEdge {
|
||||
pub point: Vec3,
|
||||
pub angle: Option<String>,
|
||||
pub height: Option<String>,
|
||||
}
|
||||
|
||||
impl FlangeEdge {
|
||||
/// An edge that takes the feature's angle and height.
|
||||
pub fn at(point: Vec3) -> Self {
|
||||
Self { point, angle: None, height: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for FlangeEdge {
|
||||
fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
|
||||
use serde::ser::SerializeMap;
|
||||
if self.angle.is_none() && self.height.is_none() {
|
||||
return self.point.serialize(s);
|
||||
}
|
||||
let mut m = s.serialize_map(None)?;
|
||||
m.serialize_entry("point", &self.point)?;
|
||||
if let Some(a) = &self.angle {
|
||||
m.serialize_entry("angle", a)?;
|
||||
}
|
||||
if let Some(h) = &self.height {
|
||||
m.serialize_entry("height", h)?;
|
||||
}
|
||||
m.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for FlangeEdge {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
|
||||
use serde::de::value::{MapAccessDeserializer, SeqAccessDeserializer};
|
||||
/// The object form; a key this build doesn't know is refused, not dropped.
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Own {
|
||||
point: Vec3,
|
||||
#[serde(default)]
|
||||
angle: Option<String>,
|
||||
#[serde(default)]
|
||||
height: Option<String>,
|
||||
}
|
||||
struct Edge;
|
||||
impl<'de> serde::de::Visitor<'de> for Edge {
|
||||
type Value = FlangeEdge;
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str("a point [x, y, z] or an edge {point, angle?, height?}")
|
||||
}
|
||||
fn visit_seq<A: serde::de::SeqAccess<'de>>(self, seq: A) -> std::result::Result<FlangeEdge, A::Error> {
|
||||
Vec3::deserialize(SeqAccessDeserializer::new(seq)).map(FlangeEdge::at)
|
||||
}
|
||||
fn visit_map<A: serde::de::MapAccess<'de>>(self, map: A) -> std::result::Result<FlangeEdge, A::Error> {
|
||||
let o = Own::deserialize(MapAccessDeserializer::new(map))?;
|
||||
Ok(FlangeEdge { point: o.point, angle: o.angle, height: o.height })
|
||||
}
|
||||
}
|
||||
d.deserialize_any(Edge)
|
||||
}
|
||||
}
|
||||
|
||||
/// Feature definitions.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
@@ -659,9 +726,10 @@ pub enum FeatureKind {
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
reverse: bool,
|
||||
},
|
||||
/// Edge flanges on sheet edges (points on the top or bottom edges).
|
||||
/// Edge flanges on sheet edges (points on the top or bottom edges). `height` and `angle`
|
||||
/// hold for every edge that doesn't carry its own (see [`FlangeEdge`]).
|
||||
SheetFlange {
|
||||
edges: Vec<Vec3>,
|
||||
edges: Vec<FlangeEdge>,
|
||||
height: String,
|
||||
angle: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -1521,9 +1589,12 @@ impl FeatureKind {
|
||||
v.extend([length, draft, thickness].into_iter().flatten().map(String::as_str));
|
||||
}
|
||||
FeatureKind::SheetContour { distance, .. } => v.push(distance),
|
||||
FeatureKind::SheetFlange { height, angle, radius, .. } => {
|
||||
FeatureKind::SheetFlange { edges, height, angle, radius, .. } => {
|
||||
v.extend([height.as_str(), angle]);
|
||||
v.extend(radius.iter().map(String::as_str));
|
||||
for e in edges {
|
||||
v.extend([&e.angle, &e.height].into_iter().flatten().map(String::as_str));
|
||||
}
|
||||
}
|
||||
FeatureKind::SheetFold { angle, radius, .. } => {
|
||||
v.push(angle);
|
||||
|
||||
Reference in new issue
Block a user