Delete Face (face.delete, and faces in the Delete selection): a timeline feature that removes faces and heals the gap from the neighbouring planes through kernel::delete_faces; a picked face brings the rest of its surface (both halves of a hole's wall); face picks are stored by persistent name; a face its neighbours can't close is refused with the kernel's reason

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-09 12:47:56 +00:00
1 parent 9219eb5dc1
commit 3db6119cc6
11 files changed
+117 -6

No files matched your search

+11 -1
View File
@@ -6,6 +6,7 @@
use serde_json::{Value, json};
use solvecraft_doc::FeatureKind;
use solvecraft_geom::Vec3;
use super::CommandSpec;
use crate::params::{bad, bool_};
@@ -63,6 +64,7 @@ fn apply(s: &mut Session, p: &Value) -> Result<Value> {
let mut entities = Vec::new();
let mut features: Vec<u64> = Vec::new();
let mut bodies: Vec<String> = Vec::new();
let mut faces: Vec<(String, Vec<Vec3>)> = Vec::new();
for x in &items {
match x {
Sel::SketchCurve { id } | Sel::SketchPoint { id } | Sel::SketchConstraint { id } => entities.push(id.clone()),
@@ -78,7 +80,10 @@ fn apply(s: &mut Session, p: &Value) -> Result<Value> {
},
Sel::Axis { .. } => skip("the origin axes can't be deleted".into()),
Sel::Body { name } => bodies.push(name.clone()),
Sel::Face { .. } => skip("Delete Face: not available yet".into()),
Sel::Face { body, point, .. } => match faces.iter_mut().find(|(b, _)| b == body) {
Some((_, ps)) => ps.push(*point),
None => faces.push((body.clone(), vec![*point])),
},
Sel::Edge { .. } | Sel::Vertex { .. } => skip("edges and vertices are deleted with their body or face".into()),
Sel::Profile { .. } => skip("a profile goes with its sketch".into()),
}
@@ -93,6 +98,11 @@ fn apply(s: &mut Session, p: &Value) -> Result<Value> {
done = true;
}
}
// Faces of a body that is going anyway are left to it; the rest become Delete Face features.
for (body, points) in faces.iter().filter(|(b, _)| !bodies.contains(b)) {
sub(s, "face.delete", json!({ "faces": points, "body": body }))?;
done = true;
}
if !features.is_empty() {
let ids: Vec<String> = features.iter().map(u64::to_string).collect();
sub(s, "timeline.delete", json!({ "features": ids }))?;
+9
View File
@@ -66,6 +66,10 @@ pub static COMMANDS: &[CommandSpec] = &[
.at("SOLID", "MODIFY")
.icon("scale")
.params("bodies: [names]; factor: expr | factors: [x, y, z] exprs; origin?: [x,y,z]"),
CommandSpec::new("face.delete", "Delete Face", delete_face)
.at("SOLID", "MODIFY")
.icon("delete")
.params("faces: [[x,y,z] points on the faces]; body?. The neighbouring planes close the gap (a fillet, chamfer, hole or boss taken off)"),
CommandSpec::new("solid.offset_face", "Offset Face", offset_face)
.at("SOLID", "MODIFY")
.icon("offset_face")
@@ -1188,6 +1192,11 @@ fn scale(s: &mut Session, p: &Value) -> Result<Value> {
add_feature(s, p, FeatureKind::Scale { bodies, origin, factor, factors })
}
fn delete_face(s: &mut Session, p: &Value) -> Result<Value> {
let faces = face_points(p, "face.delete")?;
add_feature(s, p, FeatureKind::DeleteFace { faces, body: str_(p, "body").map(str::to_string) })
}
fn offset_face(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "solid.offset_face";
let faces = face_points(p, cmd)?;