Merge remote-tracking branch 'origin/main' into feat/profiles-by-curve-ids

# Conflicts:
#	crates/doc/src/lib.rs
#	crates/sketch/src/lib.rs
This commit is contained in:
CypherNaugh_0x committed 2026-10-11 13:07:46 +02:00
commit 184e301c46
180 files changed
+29315 -1568

No files matched your search

+30 -4
View File
@@ -17,7 +17,7 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("solid.revolve", "Revolve", revolve)
.at("SOLID", "CREATE")
.icon("revolve")
.params("axis: sketch line id | x | y (sketch axes) | X|Y|Z (world); angle?: expr (default 360 deg); angle2?: expr (Two Sides: the other side's angle); direction?: symmetric (angle split either side); to?: [x,y,z] (To Object: turn until that face, vertex or point); sketch?, profiles? | face: [x,y,z] (a planar body face as the profile), operation?, targets?, name?, body_name?"),
.params("axis: sketch line id | x | y (sketch axes) | X|Y|Z (world); angle?: expr (default 360 deg); angle2?: expr (Two Sides: the other side's angle); direction?: symmetric (angle split either side, independent of axis direction); convention?: sketch_normal|legacy_axis_start (default sketch_normal; legacy retains older angular starts); to?: [x,y,z] (To Object: turn until that face, vertex or point); sketch?, profiles? | face: [x,y,z] (a planar body face as the profile), operation?, targets?, name?, body_name?"),
CommandSpec::new("solid.sweep", "Sweep", sweep)
.at("SOLID", "CREATE")
.icon("sweep")
@@ -147,7 +147,7 @@ pub fn auto_operation(s: &Session, p: &Value) -> Option<&'static str> {
let bodies: Vec<_> = st
.bodies
.iter()
.filter(|b| if parts.is_empty() { s.doc.body_component(&b.name, b.feature) == s.active_component } else { parts.contains(&b.name) })
.filter(|b| if parts.is_empty() { s.doc.model_body_component(b) == s.active_component } else { parts.contains(&b.name) })
.collect();
if bodies.is_empty() {
return Some("new");
@@ -328,7 +328,24 @@ pub(super) fn add_feature(s: &mut Session, p: &Value, kind: FeatureKind) -> Resu
};
let face_names = {
let pts = kind.face_points();
if pts.is_empty() { Vec::new() } else { solvecraft_doc::naming::face_names_at(&s.model.state(), &pts) }
if let FeatureKind::Emboss { face: Some(face), .. } = &kind {
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();
scoped.bodies.push(body.clone());
let names = solvecraft_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()));
}
names
} else if pts.is_empty() {
Vec::new()
} else {
solvecraft_doc::naming::face_names_at(&s.model.state(), &pts)
}
};
let participants = string_list(p, "participants");
if participants.len() > 10_000 || participants.iter().any(|b| b != "*" && s.model.state().body(b).is_none()) {
@@ -472,6 +489,10 @@ 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,
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 {
sketch,
profiles,
@@ -481,6 +502,7 @@ fn revolve(s: &mut Session, p: &Value) -> Result<Value> {
targets: string_list(p, "targets"),
angle2,
symmetric,
convention,
to,
};
add_feature(s, p, kind)
@@ -717,7 +739,7 @@ fn combine(s: &mut Session, p: &Value) -> Result<Value> {
if bool_(p, "new_component").unwrap_or(false) {
let name = str_(p, "component_name").map(str::to_string).unwrap_or_else(|| format!("{target} Combined"));
let c = s.execute("component.create", &json!({"name": name, "activate": false}))?;
s.execute("component.move_bodies", &json!({"bodies": [target], "component": c["component"]}))?;
super::component::transfer_constructor_result(s, &json!({"bodies": [target], "component": c["component"]}))?;
out["component"] = c["component"].clone();
}
Ok(out)
@@ -1332,3 +1354,7 @@ fn offset_face(s: &mut Session, p: &Value) -> Result<Value> {
#[cfg(test)]
#[path = "patterns_tests.rs"]
mod patterns_tests;
#[cfg(test)]
#[path = "revolve_convention_tests.rs"]
mod revolve_convention_tests;