Ruled Surface: strips off an edge chain in normal, tangent, angle or direction mode, exact planes/cylinders/cones where possible, mitred or round corners, stitched into one surface body. SurfaceRuled is noted under format 21. Closes #762.
This commit is contained in:
1 parent
ed22d663e4
commit
2a4907884b
24 files changed
+1343
-36
No files matched your search
@@ -138,6 +138,44 @@ pub enum Direction {
|
||||
Symmetric,
|
||||
}
|
||||
|
||||
/// The direction a ruled surface leaves its edges (`surface.ruled`).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RuledMode {
|
||||
/// Along the normal of the face beside each edge.
|
||||
#[default]
|
||||
Normal,
|
||||
/// Continuing the face beside each edge.
|
||||
Tangent,
|
||||
/// At an angle from a reference direction (the face's normal unless one is given).
|
||||
Angle,
|
||||
/// Along a fixed direction.
|
||||
Direction,
|
||||
}
|
||||
|
||||
impl RuledMode {
|
||||
fn is_normal(&self) -> bool {
|
||||
*self == RuledMode::Normal
|
||||
}
|
||||
}
|
||||
|
||||
/// How a ruled surface's strips meet at sharp corners of its edge chain.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RuledCorners {
|
||||
/// Extended or cut back until they meet.
|
||||
#[default]
|
||||
Mitre,
|
||||
/// Joined by a sector (inside corners are cut back).
|
||||
Round,
|
||||
}
|
||||
|
||||
impl RuledCorners {
|
||||
fn is_mitre(&self) -> bool {
|
||||
*self == RuledCorners::Mitre
|
||||
}
|
||||
}
|
||||
|
||||
/// Angular starts in older designs used the raw axis sign. New revolves measure starts
|
||||
/// along the same sketch-normal sweep direction as the kernel.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
@@ -1078,6 +1116,27 @@ pub enum FeatureKind {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
tolerance: Option<String>,
|
||||
},
|
||||
/// A strip of ruled surface `distance` wide hanging off the chain of `body`'s edges through
|
||||
/// `edges`, as a new surface body: along `mode` (`angle` from `direction`, or from the
|
||||
/// face's normal; along `direction`), from the face at `face` where it borders the chain;
|
||||
/// `flip` puts it on the other side; sharp corners as `corners` says.
|
||||
SurfaceRuled {
|
||||
body: String,
|
||||
edges: Vec<Vec3>,
|
||||
distance: String,
|
||||
#[serde(default, skip_serializing_if = "RuledMode::is_normal")]
|
||||
mode: RuledMode,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
angle: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
direction: Option<Vec3>,
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
flip: bool,
|
||||
#[serde(default, skip_serializing_if = "RuledCorners::is_mitre")]
|
||||
corners: RuledCorners,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
face: Option<Vec3>,
|
||||
},
|
||||
/// A planar surface body extended outward by `distance` past the edges through `edges`.
|
||||
SurfaceExtend {
|
||||
body: String,
|
||||
@@ -1455,6 +1514,7 @@ impl FeatureKind {
|
||||
FeatureKind::Thicken { .. } => "ThickenFeature",
|
||||
FeatureKind::SurfaceTrim { .. } => "TrimFeature",
|
||||
FeatureKind::SurfaceExtend { .. } => "ExtendFeature",
|
||||
FeatureKind::SurfaceRuled { .. } => "RuledSurfaceFeature",
|
||||
FeatureKind::Unstitch { .. } => "UnstitchFeature",
|
||||
FeatureKind::ReverseNormal { .. } => "ReverseNormalFeature",
|
||||
FeatureKind::SurfaceUntrim { .. } => "UntrimFeature",
|
||||
@@ -1530,6 +1590,7 @@ impl FeatureKind {
|
||||
FeatureKind::Thicken { .. } => "Thicken",
|
||||
FeatureKind::SurfaceTrim { .. } => "Trim",
|
||||
FeatureKind::SurfaceExtend { .. } => "Extend",
|
||||
FeatureKind::SurfaceRuled { .. } => "RuledSurface",
|
||||
FeatureKind::Unstitch { .. } => "Unstitch",
|
||||
FeatureKind::ReverseNormal { .. } => "ReverseNormal",
|
||||
FeatureKind::SurfaceUntrim { .. } => "Untrim",
|
||||
|
||||
@@ -3926,6 +3926,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
| FeatureKind::SurfaceSweep { .. }
|
||||
| FeatureKind::SurfaceLoft { .. }
|
||||
| FeatureKind::SurfaceOffset { .. }
|
||||
| FeatureKind::SurfaceRuled { .. }
|
||||
| FeatureKind::SurfaceUntrim { .. } => surface::eval(doc, vals, f, st),
|
||||
FeatureKind::Emboss { .. }
|
||||
| FeatureKind::Rib { .. }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! Evaluation of the surface features: Patch, Stitch, Thicken, Trim, Extend, Surface Extrude,
|
||||
//! Revolve, Sweep and Loft, Offset, Untrim, Unstitch and Reverse Normal (kernel `surfaces`,
|
||||
//! `surface_offset`, `surface_untrim`).
|
||||
//! Revolve, Sweep and Loft, Offset, Ruled, Untrim, Unstitch and Reverse Normal (kernel `surfaces`,
|
||||
//! `surface_offset`, `surface_ruled`, `surface_untrim`).
|
||||
|
||||
use super::*;
|
||||
use crate::{RuledCorners, RuledMode};
|
||||
use solidcraft_geom::Seg2;
|
||||
|
||||
pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState) -> Result<()> {
|
||||
@@ -234,6 +235,30 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
|
||||
st.bodies.push(ModelBody::traced_new(name, made, f.id));
|
||||
Ok(())
|
||||
}
|
||||
FeatureKind::SurfaceRuled { body, edges, distance, mode, angle, direction, flip, corners, face } => {
|
||||
let d = val(vals, distance, Kind::Length)?;
|
||||
let mode = match mode {
|
||||
RuledMode::Normal => kernel::RuledMode::Normal,
|
||||
RuledMode::Tangent => kernel::RuledMode::Tangent,
|
||||
RuledMode::Angle => {
|
||||
let a = angle.as_ref().ok_or_else(|| DocError::Invalid("a ruled surface at an angle needs `angle`".into()))?;
|
||||
kernel::RuledMode::Angle { angle: val(vals, a, Kind::Angle)?, reference: *direction }
|
||||
}
|
||||
RuledMode::Direction => kernel::RuledMode::Direction(
|
||||
direction.ok_or_else(|| DocError::Invalid("a ruled surface along a direction needs `direction`".into()))?,
|
||||
),
|
||||
};
|
||||
let corners = match corners {
|
||||
RuledCorners::Mitre => kernel::RuledCorners::Mitre,
|
||||
RuledCorners::Round => kernel::RuledCorners::Round,
|
||||
};
|
||||
let mb = st.body(body).cloned().ok_or_else(|| DocError::Unknown(format!("body `{body}`")))?;
|
||||
let r = kernel::Ruled { distance: d, mode, flip: *flip, corners, face: *face };
|
||||
let made = Traced::step(kernel::ruled_surface(&mb.body, edges, &r)?, &[&mb.traced()]);
|
||||
let name = new_name(st, f, 0);
|
||||
st.bodies.push(ModelBody::traced_new(name, made, f.id));
|
||||
Ok(())
|
||||
}
|
||||
FeatureKind::SurfaceUntrim { face, holes, outer, in_place } => {
|
||||
let i = st.bodies.iter().position(|b| b.name == face.body).ok_or_else(|| DocError::Unknown(format!("body `{}`", face.body)))?;
|
||||
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
|
||||
|
||||
@@ -152,6 +152,17 @@ macro_rules! geometry_walk {
|
||||
f(Pick(p));
|
||||
}
|
||||
}
|
||||
FeatureKind::SurfaceRuled { edges, direction, face, .. } => {
|
||||
for e in edges {
|
||||
f(Pos(e));
|
||||
}
|
||||
if let Some(d) = direction {
|
||||
f(Dir(d));
|
||||
}
|
||||
if let Some(p) = face {
|
||||
f(Pick(p));
|
||||
}
|
||||
}
|
||||
FeatureKind::Unstitch { faces, .. } | FeatureKind::ReverseNormal { faces, .. } => {
|
||||
for p in faces {
|
||||
f(Pos(p));
|
||||
|
||||
@@ -177,6 +177,7 @@ macro_rules! feature_walk {
|
||||
FeatureKind::SurfaceUntrim { face, .. } => f(B(& $($m)? face.body)),
|
||||
FeatureKind::SheetConvert { body, .. }
|
||||
| FeatureKind::SurfaceExtend { body, .. }
|
||||
| FeatureKind::SurfaceRuled { body, .. }
|
||||
| FeatureKind::Unstitch { body, .. }
|
||||
| FeatureKind::ReverseNormal { body, .. }
|
||||
| FeatureKind::MeshConvert { body, .. } => f(B(body)),
|
||||
|
||||
@@ -153,6 +153,11 @@ pub(super) fn fixtures() -> Vec<(&'static str, Value)> {
|
||||
("reverse_normal", json!({"type": "reverse_normal", "body": "B"})),
|
||||
("surface_untrim", json!({"type": "surface_untrim", "face": {"body": "B", "point": o}, "holes": [o], "outer": true})),
|
||||
("surface_extend", json!({"type": "surface_extend", "body": "B", "edges": [o], "distance": "extend.distance"})),
|
||||
(
|
||||
"surface_ruled",
|
||||
json!({"type": "surface_ruled", "body": "B", "edges": [o], "distance": "ruled.distance", "mode": "angle", "angle": "ruled.angle",
|
||||
"direction": [0, 0, 1], "face": [1, 0, 0]}),
|
||||
),
|
||||
("surface_trim", json!({"type": "surface_trim", "body": "B", "plane": plane("trim"), "keep": o})),
|
||||
("move", json!({"type": "move", "bodies": ["B"], "translate": ["move.x", "move.y", "move.z"], "angle": "move.angle"})),
|
||||
("import", json!({"type": "import", "file": "a.step", "step": ""})),
|
||||
|
||||
@@ -330,6 +330,11 @@ surface_extend
|
||||
inputs:
|
||||
Distance = extend.distance (Length)
|
||||
expressions: extend.distance
|
||||
surface_ruled
|
||||
inputs:
|
||||
Distance = ruled.distance (Length)
|
||||
Angle = ruled.angle (Angle)
|
||||
expressions: ruled.distance, ruled.angle
|
||||
surface_trim
|
||||
inputs:
|
||||
Angle = trim.angle (Angle)
|
||||
|
||||
@@ -155,6 +155,9 @@ surface_untrim
|
||||
face points: [0, 0, 0]
|
||||
surface_extend
|
||||
moves: edges[0][0], edges[0][1], edges[0][2]
|
||||
surface_ruled
|
||||
moves: direction[0], direction[2], edges[0][0], edges[0][1], edges[0][2], face[1], face[2]
|
||||
face points: [1, 0, 0]
|
||||
surface_trim
|
||||
moves: keep[0], keep[1], keep[2], plane.axis_dir[0], plane.axis_dir[1], plane.axis_origin[0], plane.axis_origin[1], plane.axis_origin[2], tool.point[0], tool.point[1], tool.point[2]
|
||||
move
|
||||
|
||||
@@ -172,6 +172,8 @@ surface_untrim
|
||||
remap_bodies: face.body
|
||||
surface_extend
|
||||
remap_bodies: body
|
||||
surface_ruled
|
||||
remap_bodies: body
|
||||
surface_trim
|
||||
remap_refs: plane.base.base.name
|
||||
remap_bodies: body, tool.body
|
||||
|
||||
@@ -170,6 +170,9 @@
|
||||
//! A `SurfaceUntrim` feature (Untrim: a `face` as `body` and `point`, optional `holes`
|
||||
//! points, `outer` and `in_place`) is also first saved in 21; 0.11.0 asks for an update
|
||||
//! instead of meeting a feature it can't read.
|
||||
//! A `SurfaceRuled` feature (Ruled Surface: `body`, `edges` points, `distance`, optional
|
||||
//! `mode`, `angle`, `direction`, `flip`, `corners` and `face`) is also first saved in 21;
|
||||
//! 0.11.0 asks for an update instead of meeting a feature it can't read.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
|
||||
@@ -354,6 +354,12 @@ macro_rules! feature_walk {
|
||||
FeatureKind::Thicken { thickness, .. } => f("Thickness", thickness, L, I),
|
||||
FeatureKind::SurfaceTrim { plane, .. } => $plane(plane, f),
|
||||
FeatureKind::SurfaceExtend { distance, .. } => f("Distance", distance, L, I),
|
||||
FeatureKind::SurfaceRuled { distance, angle, .. } => {
|
||||
f("Distance", distance, L, I);
|
||||
if let Some(a) = angle {
|
||||
f("Angle", a, A, I);
|
||||
}
|
||||
}
|
||||
FeatureKind::Unstitch { .. } | FeatureKind::ReverseNormal { .. } | FeatureKind::SurfaceUntrim { .. } => {}
|
||||
FeatureKind::SurfaceOffset { distance, tolerance, .. } => {
|
||||
f("Distance", distance, L, I);
|
||||
|
||||
Reference in new issue
Block a user