Rename SolveCraft to SolidCraft everywhere, with compatibility shims (#389)

* WIP deep rename (not for push)

* WIP2

* WIP3

* WIP4 wxs fix

* rustfmt

* Rename the remaining identifiers in merged crash-report code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-12 17:17:01 -04:00
1 parent 27415e4dd7
commit 296683229d
464 files changed
+3408 -2956

No files matched your search

+59 -59
View File
@@ -1,8 +1,8 @@
//! Solid feature commands: extrude, revolve, fillet, chamfer, primitives, combine, move.
use serde_json::{Value, json};
use solvecraft_doc::{AxisRef, Direction, Extent, FeatureKind, Operation, PointExpr, ProfileSel, expr::Kind, point_expr};
use solvecraft_geom::Vec3;
use solidcraft_doc::{AxisRef, Direction, Extent, FeatureKind, Operation, PointExpr, ProfileSel, expr::Kind, point_expr};
use solidcraft_geom::Vec3;
use super::CommandSpec;
use crate::params::{bad, bool_, expr, req_expr, str_, string_list, vec2, vec3};
@@ -125,7 +125,7 @@ fn extrude_base(s: &Session, p: &Value) -> Option<(Vec3, Vec3)> {
ProfileSel::Indices { indices } => ps.get(*indices.first()?)?.region.interior_point(),
ProfileSel::Points { points } => *points.first()?,
selection @ (ProfileSel::Curves { .. } | ProfileSel::OffsetBoundaries { .. } | ProfileSel::WholeBoundaries { .. }) => {
let indices = solvecraft_doc::profile_selection_indices(ss, &selection).ok()?;
let indices = solidcraft_doc::profile_selection_indices(ss, &selection).ok()?;
ps.get(*indices.first()?)?.region.interior_point()
}
};
@@ -210,12 +210,12 @@ pub(super) fn feature_sketch(s: &Session, p: &Value, cmd: &str) -> Result<u64> {
/// curves) stays as given. Curve picks are checked: one that names no profile, or several, is
/// refused now rather than failing later.
pub(super) fn stable_profiles(s: &Session, kind: &mut FeatureKind) -> Result<()> {
use solvecraft_doc::sketch::profile_at_point;
use solidcraft_doc::sketch::profile_at_point;
let st = s.model.state();
for (sketch, sel) in kind.profile_sels_mut() {
let Some(ss) = st.sketch(sketch) else { continue };
let indices = match sel {
ProfileSel::Indices { .. } => solvecraft_doc::profile_selection_indices(ss, sel)?,
ProfileSel::Indices { .. } => solidcraft_doc::profile_selection_indices(ss, sel)?,
ProfileSel::Points { points } => {
let Some(indices) = points.iter().map(|point| profile_at_point(&ss.profiles, *point)).collect::<Option<Vec<_>>>() else { continue };
indices
@@ -240,19 +240,19 @@ pub(super) fn stable_profiles(s: &Session, kind: &mut FeatureKind) -> Result<()>
}
}
let selection = ProfileSel::Curves { loops: normalized };
solvecraft_doc::profile_selection_indices(ss, &selection)?;
solidcraft_doc::profile_selection_indices(ss, &selection)?;
*sel = selection;
continue;
}
ProfileSel::OffsetBoundaries { .. } | ProfileSel::WholeBoundaries { .. } => {
// Explicit identities retain the caller's semantics. Validate through the
// same resolver as recomputation, including reserved-role precedence.
solvecraft_doc::profile_selection_indices(ss, sel)?;
solidcraft_doc::profile_selection_indices(ss, sel)?;
continue;
}
ProfileSel::All => continue,
};
let captured = solvecraft_doc::capture_profile_selection(ss, &indices)?;
let captured = solidcraft_doc::capture_profile_selection(ss, &indices)?;
*sel = match captured {
ProfileSel::OffsetBoundaries { associations }
if associations.iter().any(|id| {
@@ -288,7 +288,7 @@ pub(super) fn profiles(p: &Value, cmd: &str) -> Result<ProfileSel> {
match v {
Value::Object(fields) => {
if fields.get("type").and_then(Value::as_str) == Some("whole_boundaries") {
return solvecraft_doc::whole_profile_selection_from_value(v).map_err(|error| bad(cmd, error.to_string()));
return solidcraft_doc::whole_profile_selection_from_value(v).map_err(|error| bad(cmd, error.to_string()));
}
let associations = fields
.get("offset_boundaries")
@@ -384,11 +384,11 @@ fn add_feature_in(s: &mut Session, p: &Value, kind: FeatureKind, world: bool) ->
let (refs, names) = match &kind {
FeatureKind::Fillet { edges, body, .. } | FeatureKind::Chamfer { edges, body, .. } => {
let st = s.model.state();
(solvecraft_doc::edge_refs(&st, body, edges), solvecraft_doc::edge_names_for(&st, body, edges))
(solidcraft_doc::edge_refs(&st, body, edges), solidcraft_doc::edge_names_for(&st, body, edges))
}
// Sheet metal edges are named by the sheet model.
FeatureKind::SheetFlange { edges, .. } | FeatureKind::SheetHem { edges, .. } => {
(Vec::new(), solvecraft_doc::sheet::sheet_edge_names(&s.model.state(), edges))
(Vec::new(), solidcraft_doc::sheet::sheet_edge_names(&s.model.state(), edges))
}
_ => (Vec::new(), Vec::new()),
};
@@ -398,11 +398,11 @@ fn add_feature_in(s: &mut Session, p: &Value, kind: FeatureKind, world: bool) ->
let state = s.model.state();
let body = state.body(&face.body).ok_or_else(|| EngineError::Other("selected cylinder body no longer exists".into()))?;
let frame = face.body_frame(&s.doc, body).map_err(|e| EngineError::Other(e.to_string()))?;
let inverse = solvecraft_doc::mat_inverse(&frame).ok_or_else(|| EngineError::Other("invalid cylinder placement".into()))?;
let at = solvecraft_doc::apply_point(&solvecraft_doc::mat_mul(&inverse, &s.active_component_frame()), face.at);
let mut scoped = solvecraft_doc::ModelState::default();
let inverse = solidcraft_doc::mat_inverse(&frame).ok_or_else(|| EngineError::Other("invalid cylinder placement".into()))?;
let at = solidcraft_doc::apply_point(&solidcraft_doc::mat_mul(&inverse, &s.active_component_frame()), face.at);
let mut scoped = solidcraft_doc::ModelState::default();
scoped.bodies.push(body.clone());
let names = solvecraft_doc::naming::face_names_at(&scoped, &[at]);
let names = solidcraft_doc::naming::face_names_at(&scoped, &[at]);
if names.len() != 1 || names.first().is_none_or(|name| name.is_empty()) {
return Err(EngineError::Other("selected cylinder face has no persistent identity".into()));
}
@@ -410,7 +410,7 @@ fn add_feature_in(s: &mut Session, p: &Value, kind: FeatureKind, world: bool) ->
} else if pts.is_empty() {
Vec::new()
} else {
solvecraft_doc::naming::face_names_at(&s.model.state(), &pts)
solidcraft_doc::naming::face_names_at(&s.model.state(), &pts)
}
};
// Primitives have no `targets` of their own: their `targets` are the bodies taking part (#259).
@@ -578,7 +578,7 @@ fn revolve(s: &mut Session, p: &Value) -> Result<Value> {
let symmetric =
matches!(str_(p, "direction").map(str::to_ascii_lowercase).as_deref(), Some("symmetric")) || bool_(p, "symmetric").unwrap_or(false);
let convention = match p.get("convention") {
None => solvecraft_doc::RevolveConvention::SketchNormal,
None => solidcraft_doc::RevolveConvention::SketchNormal,
Some(value) => serde_json::from_value(value.clone()).map_err(|_| bad(cmd, "`convention` must be sketch_normal or legacy_axis_start"))?,
};
let kind = FeatureKind::Revolve {
@@ -729,7 +729,7 @@ fn press_pull(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(fp) = vec3(face)
&& super::face::face_is_planar(s, fp) == Some(false)
{
let name = solvecraft_doc::naming::face_names_at(&s.model.state(), &[fp]).into_iter().next().unwrap_or_default();
let name = solidcraft_doc::naming::face_names_at(&s.model.state(), &[fp]).into_iter().next().unwrap_or_default();
let fid = name.strip_prefix('F').and_then(|r| r.split(':').next()).and_then(|x| x.parse::<u64>().ok());
if name.contains(":blend") {
let f = fid.and_then(|id| s.doc.feature(id)).cloned().ok_or_else(|| bad(cmd, "the blend's feature is gone"))?;
@@ -757,8 +757,8 @@ fn fillet(s: &mut Session, p: &Value) -> Result<Value> {
let radius = req_expr(cmd, p, "radius")?;
check_expr(s, &radius, Kind::Length, cmd, "radius")?;
let style = match str_(p, "type").map(str::to_ascii_lowercase).as_deref() {
None | Some("constant") | Some("rolling_ball") => solvecraft_doc::FilletStyle::Constant,
Some("chord") | Some("chord_length") => solvecraft_doc::FilletStyle::Chord,
None | Some("constant") | Some("rolling_ball") => solidcraft_doc::FilletStyle::Constant,
Some("chord") | Some("chord_length") => solidcraft_doc::FilletStyle::Chord,
Some("variable") => {
let radius2 = req_expr(cmd, p, "radius2")?;
check_expr(s, &radius2, Kind::Length, cmd, "radius2")?;
@@ -781,13 +781,13 @@ fn fillet(s: &mut Session, p: &Value) -> Result<Value> {
mid.push((t, r));
}
let smooth = p.get("smooth").and_then(Value::as_bool).unwrap_or(false);
solvecraft_doc::FilletStyle::Variable { radius2, start, mid, smooth }
solidcraft_doc::FilletStyle::Variable { radius2, start, mid, smooth }
}
Some(o) => return Err(bad(cmd, format!("unknown fillet type `{o}` (constant, chord or variable)"))),
};
let fit = match str_(p, "fit").map(str::to_ascii_lowercase).as_deref() {
None | Some("exact") => solvecraft_doc::FilletFit::Exact,
Some("reduce") => solvecraft_doc::FilletFit::Reduce,
None | Some("exact") => solidcraft_doc::FilletFit::Exact,
Some("reduce") => solidcraft_doc::FilletFit::Reduce,
Some(o) => return Err(bad(cmd, format!("unknown fit `{o}` (exact or reduce)"))),
};
let mut out = add_feature(s, p, FeatureKind::Fillet { edges, radius, body: str_(p, "body").map(str::to_string), style, fit })?;
@@ -909,19 +909,19 @@ fn axis_line(p: &Value, cmd: &str) -> Result<(Vec3, Vec3)> {
}
/// A pattern feature, or for `components`, new occurrences of them at each placement.
fn finish_pattern(s: &mut Session, p: &Value, features: Vec<String>, pattern: solvecraft_doc::PatternKind, bodies: Vec<String>) -> Result<Value> {
fn finish_pattern(s: &mut Session, p: &Value, features: Vec<String>, pattern: solidcraft_doc::PatternKind, bodies: Vec<String>) -> Result<Value> {
let comps = string_list(p, "components");
if comps.is_empty() {
return add_feature(s, p, FeatureKind::Pattern { features, pattern, bodies });
}
let cmd = "pattern (components)";
let mats = solvecraft_doc::pattern_matrices(&s.doc, &s.model.state(), &pattern)?;
let mats = solidcraft_doc::pattern_matrices(&s.doc, &s.model.state(), &pattern)?;
let mut made = Vec::new();
for c in &comps {
let id = s.doc.find_component(c).filter(|id| *id != 0).ok_or_else(|| bad(cmd, format!("no component `{c}`")))?;
let src = s.doc.occurrence_of(id).cloned().ok_or_else(|| bad(cmd, format!("component `{c}` has no occurrence")))?;
for m in &mats {
made.push(s.doc_mut().add_occurrence(id, src.parent, solvecraft_doc::mat_mul(m, &src.transform))?);
made.push(s.doc_mut().add_occurrence(id, src.parent, solidcraft_doc::mat_mul(m, &src.transform))?);
}
}
Ok(json!({"occurrences": made}))
@@ -960,7 +960,7 @@ fn pattern_rect(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(c) = &spacing2 {
check_expr(s, c, Kind::Length, cmd, "spacing2")?;
}
let pattern = solvecraft_doc::PatternKind::Rectangular { dir1, count1, spacing1, dir2, count2, spacing2 };
let pattern = solidcraft_doc::PatternKind::Rectangular { dir1, count1, spacing1, dir2, count2, spacing2 };
finish_pattern(s, p, features, pattern, bodies)
}
@@ -972,7 +972,7 @@ fn pattern_circ(s: &mut Session, p: &Value) -> Result<Value> {
check_expr(s, &count, Kind::Unitless, cmd, "count")?;
let angle = expr(p, "angle").unwrap_or_else(|| "360 deg".into());
check_expr(s, &angle, Kind::Angle, cmd, "angle")?;
let pattern = solvecraft_doc::PatternKind::Circular { origin, axis, count, angle };
let pattern = solidcraft_doc::PatternKind::Circular { origin, axis, count, angle };
finish_pattern(s, p, features, pattern, bodies)
}
@@ -998,7 +998,7 @@ fn pattern_path(s: &mut Session, p: &Value) -> Result<Value> {
Some(o) => return Err(bad(cmd, format!("unknown orientation `{o}` (identical or path)"))),
};
let flip = bool_(p, "flip").unwrap_or(false);
let pattern = solvecraft_doc::PatternKind::Path { path_sketch, path, count, spacing, extent, flip, orient };
let pattern = solidcraft_doc::PatternKind::Path { path_sketch, path, count, spacing, extent, flip, orient };
finish_pattern(s, p, features, pattern, bodies)
}
@@ -1027,7 +1027,7 @@ pub(super) fn origin_plane_alias(name: &str) -> Option<&'static str> {
/// A construction plane may not take an origin plane's name (`XY`, `Top`...): a plane
/// parameter could then mean either.
pub(super) fn check_plane_name(name: &str, cmd: &str) -> Result<()> {
if solvecraft_geom::Plane::named(name.trim()).is_some() {
if solidcraft_geom::Plane::named(name.trim()).is_some() {
return Err(bad(
cmd,
format!("`{}` is reserved for an origin plane (XY, XZ, YZ, Top, Front, Right); give the construction plane another name", name.trim()),
@@ -1040,8 +1040,8 @@ pub(super) fn check_plane_name(name: &str, cmd: &str) -> Result<()> {
/// feature id; kept by name, so the feature follows the plane when it changes), or an
/// explicit plane `{origin, x_dir, y_dir}` / `{origin, normal}`. Every command that takes a
/// plane reads it here.
pub(super) fn plane_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result<solvecraft_doc::PlaneRef> {
use solvecraft_doc::PlaneRef;
pub(super) fn plane_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result<solidcraft_doc::PlaneRef> {
use solidcraft_doc::PlaneRef;
const WANT: &str = "XY, XZ, YZ, a construction plane name or id, or {origin, normal}";
let construction = |r: &str| match s.doc.find_feature(r) {
Some(f) if matches!(f.kind, FeatureKind::ConstructionPlane { .. }) => Ok(PlaneRef::Construction { name: f.name.clone() }),
@@ -1049,7 +1049,7 @@ pub(super) fn plane_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result<s
None => Err(bad(cmd, format!("no plane `{r}` (a plane is {WANT})"))),
};
match v {
Some(Value::String(n)) if solvecraft_geom::Plane::named(n.trim()).is_some() => {
Some(Value::String(n)) if solidcraft_geom::Plane::named(n.trim()).is_some() => {
if let Some(f) = s.doc.find_feature(n.trim())
&& matches!(f.kind, FeatureKind::ConstructionPlane { .. })
{
@@ -1076,8 +1076,8 @@ pub(super) fn plane_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result<s
Some(x) => vec3(x).ok_or_else(|| bad(cmd, "the plane's `origin` must be a point [x, y, z]"))?,
};
let pl = match (o.get("x_dir").and_then(vec3), o.get("y_dir").and_then(vec3), o.get("normal").and_then(vec3)) {
(Some(x), Some(y), _) => solvecraft_geom::Plane::new(origin, x, y),
(_, _, Some(n)) => solvecraft_geom::Plane::from_normal(origin, n),
(Some(x), Some(y), _) => solidcraft_geom::Plane::new(origin, x, y),
(_, _, Some(n)) => solidcraft_geom::Plane::from_normal(origin, n),
_ => None,
};
Ok(PlaneRef::Custom { plane: pl.ok_or_else(|| bad(cmd, "plane needs x_dir and y_dir, or normal"))? })
@@ -1094,12 +1094,12 @@ fn plane_offset(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(face) = p.get("base").filter(|b| b.get("face").is_some()) {
let base = super::sketch::face::capture(s, face, cmd)?;
super::sketch::face::capture_positions(s, &base)?;
let kind = FeatureKind::ConstructionPlane { plane: solvecraft_doc::PlaneRef::Offset { base: Box::new(base), distance: d } };
let kind = FeatureKind::ConstructionPlane { plane: solidcraft_doc::PlaneRef::Offset { base: Box::new(base), distance: d } };
// The capture is already in the authoring frame.
return add_feature_in(s, p, kind, false);
}
let base = plane_param(s, p.get("base"), cmd)?;
add_feature(s, p, FeatureKind::ConstructionPlane { plane: solvecraft_doc::PlaneRef::Offset { base: Box::new(base), distance: d } })
add_feature(s, p, FeatureKind::ConstructionPlane { plane: solidcraft_doc::PlaneRef::Offset { base: Box::new(base), distance: d } })
}
fn plane_angle(s: &mut Session, p: &Value) -> Result<Value> {
@@ -1111,7 +1111,7 @@ fn plane_angle(s: &mut Session, p: &Value) -> Result<Value> {
add_feature(
s,
p,
FeatureKind::ConstructionPlane { plane: solvecraft_doc::PlaneRef::AtAngle { base: Box::new(base), axis_origin, axis_dir, angle } },
FeatureKind::ConstructionPlane { plane: solidcraft_doc::PlaneRef::AtAngle { base: Box::new(base), axis_origin, axis_dir, angle } },
)
}
@@ -1119,11 +1119,11 @@ fn split_face(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "solid.split_face";
let faces = face_points(p, cmd)?;
let body = str_(p, "body").map(str::to_string);
let xy = || solvecraft_doc::PlaneRef::Origin { name: "XY".into() };
let xy = || solidcraft_doc::PlaneRef::Origin { name: "XY".into() };
let kind = if let Some(t) = p.get("tool").filter(|t| !t.is_null()) {
let tb = t.get("body").and_then(Value::as_str).ok_or_else(|| bad(cmd, "`tool.body` must name a body"))?.to_string();
let point = t.get("point").and_then(crate::params::vec3).ok_or_else(|| bad(cmd, "`tool.point` must be [x, y, z] on the tool face"))?;
FeatureKind::SplitFace { faces, body, plane: xy(), tool: Some(solvecraft_doc::FaceAt { body: tb, point }), sketch: None }
FeatureKind::SplitFace { faces, body, plane: xy(), tool: Some(solidcraft_doc::FaceAt { body: tb, point }), sketch: None }
} else if p.get("sketch").is_some_and(|v| !v.is_null()) {
let sketch = sketch_id(s, p.get("sketch"), cmd, "sketch")?;
FeatureKind::SplitFace { faces, body, plane: xy(), tool: None, sketch: Some(sketch) }
@@ -1146,7 +1146,7 @@ fn split_body(s: &mut Session, p: &Value) -> Result<Value> {
let point = t.get("point").and_then(crate::params::vec3).ok_or_else(|| bad(cmd, "`tool.point` must be [x, y, z] on the tool face"))?;
let st = s.model.state();
let tbody = st.body(&tb).ok_or_else(|| bad(cmd, format!("no body `{tb}`")))?;
if solvecraft_doc::appearance::face_index_at(tbody, point).is_none() {
if solidcraft_doc::appearance::face_index_at(tbody, point).is_none() {
return Err(bad(cmd, "`tool.point` is not on a face of the tool body"));
}
return add_feature(
@@ -1154,8 +1154,8 @@ fn split_body(s: &mut Session, p: &Value) -> Result<Value> {
p,
FeatureKind::Split {
body,
plane: solvecraft_doc::PlaneRef::Origin { name: "XY".into() },
tool: Some(solvecraft_doc::FaceAt { body: tb, point }),
plane: solidcraft_doc::PlaneRef::Origin { name: "XY".into() },
tool: Some(solidcraft_doc::FaceAt { body: tb, point }),
},
);
}
@@ -1221,13 +1221,13 @@ fn hole(s: &mut Session, p: &Value) -> Result<Value> {
let n = ss.plane.normal();
let first =
ids.first().and_then(|id| ss.sketch.points.iter().find(|q| &q.id == id)).map(|q| ss.plane.to_world(q.pos)).unwrap_or_default();
Some((solvecraft_doc::SketchPoints { sketch, ids }, first, -n))
Some((solidcraft_doc::SketchPoints { sketch, ids }, first, -n))
}
_ => None,
};
let thread = str_(p, "thread").map(|t| t.trim().to_string()).filter(|t| !t.is_empty());
if let Some(t) = &thread
&& solvecraft_doc::parse_metric_thread(t).is_none()
&& solidcraft_doc::parse_metric_thread(t).is_none()
{
return Err(bad(cmd, format!("unknown thread `{t}` (ISO metric, like M6 or M6x0.75)")));
}
@@ -1268,7 +1268,7 @@ fn hole(s: &mut Session, p: &Value) -> Result<Value> {
format!("{d} mm")
}
(None, Some(t), None) => {
let (major, pitch) = solvecraft_doc::parse_metric_thread(t).ok_or_else(|| bad(cmd, "thread"))?;
let (major, pitch) = solidcraft_doc::parse_metric_thread(t).ok_or_else(|| bad(cmd, "thread"))?;
format!("{} mm", ((major - pitch) * 1000.0).round() / 1000.0)
}
(None, None, None) => return Err(bad(cmd, "needs `diameter`, `thread` (a tapped hole) or `clearance` (a bolt size)")),
@@ -1284,12 +1284,12 @@ fn hole(s: &mut Session, p: &Value) -> Result<Value> {
};
let kind = match str_(p, "type").unwrap_or("simple") {
"simple" => match expr(p, "tip_angle") {
Some(t) if depth.is_some() => solvecraft_doc::HoleKind::Drilled { tip_angle: t },
_ => solvecraft_doc::HoleKind::Simple,
Some(t) if depth.is_some() => solidcraft_doc::HoleKind::Drilled { tip_angle: t },
_ => solidcraft_doc::HoleKind::Simple,
},
"drilled" => solvecraft_doc::HoleKind::Drilled { tip_angle: expr(p, "tip_angle").unwrap_or_else(|| "118 deg".into()) },
"counterbore" => solvecraft_doc::HoleKind::Counterbore { cb_diameter: get("cb_diameter")?, cb_depth: get("cb_depth")? },
"countersink" => solvecraft_doc::HoleKind::Countersink {
"drilled" => solidcraft_doc::HoleKind::Drilled { tip_angle: expr(p, "tip_angle").unwrap_or_else(|| "118 deg".into()) },
"counterbore" => solidcraft_doc::HoleKind::Counterbore { cb_diameter: get("cb_diameter")?, cb_depth: get("cb_depth")? },
"countersink" => solidcraft_doc::HoleKind::Countersink {
cs_diameter: get("cs_diameter")?,
cs_angle: expr(p, "cs_angle").unwrap_or_else(|| "90 deg".into()),
},
@@ -1309,11 +1309,11 @@ fn sketch_hole_points(s: &Session, sketch: u64) -> Vec<String> {
let mut ends: Vec<usize> = Vec::new();
for c in &ss.sketch.curves {
match &c.kind {
solvecraft_sketch::CurveKind::Circle { .. } => {}
solvecraft_sketch::CurveKind::Arc { a, b, .. } | solvecraft_sketch::CurveKind::Line { a, b } => ends.extend([*a, *b]),
solvecraft_sketch::CurveKind::Ellipse { m, .. } => ends.push(*m),
solvecraft_sketch::CurveKind::Spline { pts, .. } => ends.extend(pts.iter().copied()),
solvecraft_sketch::CurveKind::Conic { a, b, apex, .. } => ends.extend([*a, *b, *apex]),
solidcraft_sketch::CurveKind::Circle { .. } => {}
solidcraft_sketch::CurveKind::Arc { a, b, .. } | solidcraft_sketch::CurveKind::Line { a, b } => ends.extend([*a, *b]),
solidcraft_sketch::CurveKind::Ellipse { m, .. } => ends.push(*m),
solidcraft_sketch::CurveKind::Spline { pts, .. } => ends.extend(pts.iter().copied()),
solidcraft_sketch::CurveKind::Conic { a, b, apex, .. } => ends.extend([*a, *b, *apex]),
}
}
ss.sketch.points.iter().enumerate().filter(|(i, p)| p.id != "origin" && !ends.contains(i)).map(|(_, p)| p.id.clone()).collect()
@@ -1410,10 +1410,10 @@ fn loft(s: &mut Session, p: &Value) -> Result<Value> {
if !st.sketch(sketch).is_some_and(|ss| ss.sketch.points.iter().any(|q| q.id == pt)) {
return Err(bad(cmd, format!("no point `{pt}` in the section's sketch")));
}
sections.push(solvecraft_doc::LoftSection { sketch, profiles: solvecraft_doc::ProfileSel::All, point: Some(pt.to_string()) });
sections.push(solidcraft_doc::LoftSection { sketch, profiles: solidcraft_doc::ProfileSel::All, point: Some(pt.to_string()) });
continue;
}
sections.push(solvecraft_doc::LoftSection { sketch, profiles: profiles(sec, cmd)?, point: None });
sections.push(solidcraft_doc::LoftSection { sketch, profiles: profiles(sec, cmd)?, point: None });
}
add_feature(s, p, FeatureKind::Loft { sections, operation: operation(p, cmd)?, targets: string_list(p, "targets") })
}
@@ -1426,7 +1426,7 @@ fn thread(s: &mut Session, p: &Value) -> Result<Value> {
.ok_or_else(|| bad(cmd, "`face` must be a point [x, y, z] on a cylindrical face"))?;
let designation = str_(p, "designation").map(|t| t.trim().to_string()).filter(|t| !t.is_empty());
if let Some(t) = &designation
&& solvecraft_doc::parse_metric_thread(t).is_none()
&& solidcraft_doc::parse_metric_thread(t).is_none()
{
return Err(bad(cmd, format!("unknown thread `{t}` (ISO metric, like M8 or M8x1)")));
}