mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 11:05:56 +00:00
doc: an extrude's taper saves how it closes the profile's corners
Extent.corner (preserve_curvature by default, tangent or round), saved only when not the default (one pending format change), and the solid.extrude `corner` parameter. draft_corner_options reports what each rule gives at each corner of a feature's profiles, for a dialog to offer the others where the chosen one is a poor fit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
@@ -274,6 +274,10 @@ impl ExtrudeConvention {
|
||||
}
|
||||
}
|
||||
|
||||
fn draft_corner_is_default(c: &solidcraft_kernel::DraftCorner) -> bool {
|
||||
*c == solidcraft_kernel::DraftCorner::default()
|
||||
}
|
||||
|
||||
/// Extent of an extrude.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, solidcraft_derive::Walk)]
|
||||
pub struct Extent {
|
||||
@@ -301,6 +305,10 @@ pub struct Extent {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[walk(angle = "Taper angle")]
|
||||
pub taper: Option<Expr>,
|
||||
/// How a taper closes the profile's sharp corners ([`solidcraft_kernel::draft_corners`] says
|
||||
/// what each rule gives at each corner).
|
||||
#[serde(default, skip_serializing_if = "draft_corner_is_default")]
|
||||
pub corner: solidcraft_kernel::DraftCorner,
|
||||
/// To Object: end at the face (or vertex/point) at this point, instead of `distance`.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub to: Option<FacePick>,
|
||||
|
||||
+15
-1
@@ -2440,6 +2440,20 @@ fn refuse_loft_holes(r: &Region2) -> Result<()> {
|
||||
Err(DocError::Invalid("not supported yet: lofting a section with holes to a point".into()))
|
||||
}
|
||||
|
||||
/// What each way of closing a taper's corners ([`kernel::DraftCorner`]) gives at each sharp
|
||||
/// corner of the profiles `sel` picks in `ss`, extruded `distance` with a taper of `taper` radians
|
||||
/// (positive grows the profile), the profiles merged as an extrude merges them: per corner, how
|
||||
/// far each offset curve runs on (or is trimmed), whether the corner cuts in, or why the rule
|
||||
/// refuses it. A dialog offers the other rules where the chosen one is a poor fit.
|
||||
pub fn draft_corner_options(ss: &SolvedSketch, sel: &ProfileSel, distance: f64, taper: f64) -> Result<Vec<kernel::CornerReport>> {
|
||||
let regions = solidcraft_sketch::merge_regions(&select_profiles(ss, sel)?);
|
||||
let mut out = Vec::new();
|
||||
for r in ®ions {
|
||||
out.extend(kernel::draft_corners(r, distance, taper)?);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn select_profiles(ss: &SolvedSketch, sel: &ProfileSel) -> Result<Vec<Region2>> {
|
||||
let ps = &ss.profiles;
|
||||
if ps.is_empty() {
|
||||
@@ -4560,7 +4574,7 @@ fn feature_tools_with_constructor(
|
||||
.map(|r| {
|
||||
let roles =
|
||||
crate::lineage::ProfileRoles { sketch: Some(&ss), region: Some(r), caps: caps.clone(), thin: thin_roles.as_ref() };
|
||||
let body = kernel::extrude_tapered_between(&start_plane, r, 0.0, len, 0.0, sign, taper, kernel::DraftCorner::default())?;
|
||||
let body = kernel::extrude_tapered_between(&start_plane, r, 0.0, len, 0.0, sign, taper, extent.corner)?;
|
||||
Ok(roles.tool(body, None))
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
@@ -1630,6 +1630,7 @@ mod tests {
|
||||
start_offset: None,
|
||||
through_all: false,
|
||||
taper: Some("0 deg".into()),
|
||||
corner: Default::default(),
|
||||
to: Some(crate::FacePick(solidcraft_geom::Vec3::new(0.0, 0.0, 10.0))),
|
||||
to_offset: Some("offset + 1 mm".into()),
|
||||
from: Some(crate::FacePick(solidcraft_geom::Vec3::new(0.0, 0.0, 5.0))),
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
//! An extrude's taper saves how it closes the profile's sharp corners: `extent.corner`,
|
||||
//! `tangent` or `round` (each offset curve continued straight along its end tangent, or an arc
|
||||
//! about the corner); absent for `preserve_curvature`, the default, where each wall continues as
|
||||
//! itself until it meets its neighbour. An older design has none and takes the default, the rule
|
||||
//! its tapers were built with before SMLib (the walls meeting where the curves' offsets cross). The
|
||||
//! last release refuses a design with this change (the newer format number).
|
||||
@@ -464,6 +464,7 @@ mod tests {
|
||||
start_offset: None,
|
||||
through_all: false,
|
||||
taper: None,
|
||||
corner: Default::default(),
|
||||
to: None,
|
||||
to_offset: None,
|
||||
from: None,
|
||||
|
||||
@@ -76,10 +76,10 @@ pub use document::*;
|
||||
pub use eval::KeptResults;
|
||||
pub use eval::{
|
||||
EdgeRef, FeatureResult, Model, ModelBody, ModelState, PlacedBodies, RevolveExtent, SavedModel, SolvedSketch, ThreadInfo, blend_target,
|
||||
curve_pick_error, edge_names_for, edge_refs, face_point_names, face_point_state, hole_direction_at, import_body_names, map_wire,
|
||||
metric_thread_sizes, object_height, parse_metric_thread, pattern_matrices, revolve_extent, revolve_extent_with_convention, select_profiles,
|
||||
sketch_hole_direction, sketch_line_plane, state_in_frame, state_in_frame_at, suggested_thread, tangent_face_chain, through_all_distance,
|
||||
world_state, world_state_reusing,
|
||||
curve_pick_error, draft_corner_options, edge_names_for, edge_refs, face_point_names, face_point_state, hole_direction_at, import_body_names,
|
||||
map_wire, metric_thread_sizes, object_height, parse_metric_thread, pattern_matrices, revolve_extent, revolve_extent_with_convention,
|
||||
select_profiles, sketch_hole_direction, sketch_line_plane, state_in_frame, state_in_frame_at, suggested_thread, tangent_face_chain,
|
||||
through_all_distance, world_state, world_state_reusing,
|
||||
};
|
||||
pub use expr::Expr;
|
||||
pub use names::{BodyName, FeatureId, FeatureName, SketchId, copy_name};
|
||||
|
||||
@@ -453,6 +453,7 @@ mod tests {
|
||||
start_offset: None,
|
||||
through_all: false,
|
||||
taper: None,
|
||||
corner: Default::default(),
|
||||
to: None,
|
||||
to_offset: None,
|
||||
from: None,
|
||||
@@ -635,6 +636,7 @@ mod tests {
|
||||
start_offset: None,
|
||||
through_all: false,
|
||||
taper: None,
|
||||
corner: Default::default(),
|
||||
to: None,
|
||||
to_offset: None,
|
||||
from: None,
|
||||
|
||||
@@ -40,6 +40,7 @@ fn plate_doc() -> (Document, u64) {
|
||||
start_offset: None,
|
||||
through_all: false,
|
||||
taper: None,
|
||||
corner: Default::default(),
|
||||
to: None,
|
||||
to_offset: None,
|
||||
from: None,
|
||||
@@ -281,6 +282,7 @@ fn box_fillet_cut_timeline() {
|
||||
start_offset: None,
|
||||
through_all: false,
|
||||
taper: None,
|
||||
corner: Default::default(),
|
||||
to: None,
|
||||
to_offset: None,
|
||||
from: None,
|
||||
|
||||
@@ -50,6 +50,7 @@ impl Walk for solidcraft_sketch::PatternAlignment {}
|
||||
impl Walk for solidcraft_kernel::StepNode {}
|
||||
impl Walk for crate::document::Operation {}
|
||||
impl Walk for solidcraft_kernel::Continuity {}
|
||||
impl Walk for solidcraft_kernel::DraftCorner {}
|
||||
impl Walk for crate::document::Direction {}
|
||||
impl Walk for crate::document::FilletFit {}
|
||||
impl Walk for crate::document::FilletCorner {}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
.key("E")
|
||||
.contract(&EXTRUDE_CONTRACT)
|
||||
.output(output_schema_for::<FeatureResult>)
|
||||
.params("distance: expr (or through_all: true); taper?: angle expr (positive: outward, so the far end is larger along the extrusion direction; negative: inward, so the far end is smaller); sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [{curves: [ids]}] (the profile whose outer loop is made of exactly these curves, or pieces of them; found again on every recompute, so it follows parameter changes; islands inside are its holes; an error when no profile or several match) | [{point:[x,y]}] (index and point picks are stored by their curves when that finds the same profile; a pick on a drawn profile that reference geometry, such as the projected outline of the face the sketch is on, cuts into regions takes the whole drawn profile); exact_regions?: bool (each index or point pick is exactly the region it names, as the dialogs send); default: every drawn profile, except that a loop inside another is its hole and a loop inside that hole is material again; face?: [x,y,z] (extrude a planar body face instead of a sketch profile); faces?: [[x,y,z],…] (coplanar faces of one body); face_body?: name (whose face, where faces of several bodies meet at the point); direction?: positive|negative|symmetric (distance is total thickness, split evenly around the start plane); convention?: total_distance|legacy_per_side (default total_distance; legacy preserves older symmetric extents); distance2?: expr (Two Sides: a second distance the other way from the first side, which may be through_all or to; with direction: negative the first side goes against the normal and distance2 along it); start_offset?: expr (along the sketch normal, also with direction: negative); to?: [x,y,z] (To Object: a face, vertex or point; to_offset?: expr past it); from?: [x,y,z] (From Object: start at that face or point); operation?: new|join|cut|intersect|auto (cut into a body, otherwise join existing solids; new in an empty component); targets?: [body] (Join from a face, or from a sketch on a body's face, defaults to that face's body; explicit targets or participants override); name?; body_name?; thin?: true (Thin: a wall along sketch curves instead of the profiles) with curves?: [curve ids] (open chains and closed loops, joined where they meet end to end; default: every drawn curve of the sketch), thickness: expr, side?: center|inside|outside|two_sides (a closed loop's inside is the area it encloses, an open chain's the side it bends toward, a straight line's the left of its first curve as drawn; default center), thickness2?: expr (two_sides: the thickness outside); corners are mitred, crossing walls are one"),
|
||||
.params("distance: expr (or through_all: true); taper?: angle expr (positive: outward, so the far end is larger along the extrusion direction; negative: inward, so the far end is smaller); corner?: preserve_curvature|tangent|round (how a taper closes a sharp corner its offset opens: each wall continues as itself, a line's plane or an arc's cone, until it meets its neighbour; each continues straight along its end tangent; or an arc about the corner. Default preserve_curvature, which needs lines and arcs at the corners); sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [{curves: [ids]}] (the profile whose outer loop is made of exactly these curves, or pieces of them; found again on every recompute, so it follows parameter changes; islands inside are its holes; an error when no profile or several match) | [{point:[x,y]}] (index and point picks are stored by their curves when that finds the same profile; a pick on a drawn profile that reference geometry, such as the projected outline of the face the sketch is on, cuts into regions takes the whole drawn profile); exact_regions?: bool (each index or point pick is exactly the region it names, as the dialogs send); default: every drawn profile, except that a loop inside another is its hole and a loop inside that hole is material again; face?: [x,y,z] (extrude a planar body face instead of a sketch profile); faces?: [[x,y,z],…] (coplanar faces of one body); face_body?: name (whose face, where faces of several bodies meet at the point); direction?: positive|negative|symmetric (distance is total thickness, split evenly around the start plane); convention?: total_distance|legacy_per_side (default total_distance; legacy preserves older symmetric extents); distance2?: expr (Two Sides: a second distance the other way from the first side, which may be through_all or to; with direction: negative the first side goes against the normal and distance2 along it); start_offset?: expr (along the sketch normal, also with direction: negative); to?: [x,y,z] (To Object: a face, vertex or point; to_offset?: expr past it); from?: [x,y,z] (From Object: start at that face or point); operation?: new|join|cut|intersect|auto (cut into a body, otherwise join existing solids; new in an empty component); targets?: [body] (Join from a face, or from a sketch on a body's face, defaults to that face's body; explicit targets or participants override); name?; body_name?; thin?: true (Thin: a wall along sketch curves instead of the profiles) with curves?: [curve ids] (open chains and closed loops, joined where they meet end to end; default: every drawn curve of the sketch), thickness: expr, side?: center|inside|outside|two_sides (a closed loop's inside is the area it encloses, an open chain's the side it bends toward, a straight line's the left of its first curve as drawn; default center), thickness2?: expr (two_sides: the thickness outside); corners are mitred, crossing walls are one"),
|
||||
CommandSpec::new("solid.revolve", "Revolve", revolve)
|
||||
.at("SOLID", "CREATE")
|
||||
.icon("revolve")
|
||||
@@ -1108,6 +1108,10 @@ pub(super) fn extent_param(s: &Session, p: &Value, cmd: &str) -> Result<Extent>
|
||||
if let Some(t) = &taper {
|
||||
check_expr(s, t, Kind::Angle, cmd, "taper")?;
|
||||
}
|
||||
let corner = match p.get("corner") {
|
||||
None => solidcraft_kernel::DraftCorner::default(),
|
||||
Some(value) => serde_json::from_value(value.clone()).map_err(|_| bad(cmd, "`corner` must be preserve_curvature, tangent or round"))?,
|
||||
};
|
||||
let direction = match str_(p, "direction").map(str::to_ascii_lowercase).as_deref() {
|
||||
None | Some("positive") | Some("one_side") => Direction::Positive,
|
||||
Some("negative") | Some("reverse") | Some("flip") => Direction::Negative,
|
||||
@@ -1136,6 +1140,7 @@ pub(super) fn extent_param(s: &Session, p: &Value, cmd: &str) -> Result<Extent>
|
||||
start_offset,
|
||||
through_all,
|
||||
taper,
|
||||
corner,
|
||||
to: p.get("to").and_then(vec3).map(FacePick),
|
||||
to_offset: expr(p, "to_offset"),
|
||||
from: p.get("from").and_then(vec3).map(FacePick),
|
||||
|
||||
Reference in New Issue
Block a user