forked from 1b-agent/fieldcraft-upstream
ui: Extrude to Surface form picks the source face, then the target surface (#400)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
4ac31d3988
commit
c077131285
3 files changed
+68
-4
No files matched your search
@@ -164,8 +164,14 @@ fn form(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens) {
|
|||||||
selection::Target::Position => "Puts the point at what is selected: a vertex or node, the centre of a hole (its faces or rim), or the centroid of faces and edges",
|
selection::Target::Position => "Puts the point at what is selected: a vertex or node, the centre of a hole (its faces or rim), or the centroid of faces and edges",
|
||||||
selection::Target::DatumPoint => "Puts the point on what is selected: a vertex, an edge's midpoint, a face's centre, or the centre of a hole or of several items",
|
selection::Target::DatumPoint => "Puts the point on what is selected: a vertex, an edge's midpoint, a face's centre, or the centre of a hole or of several items",
|
||||||
selection::Target::Bodies => "The selected bodies (Select: Bodies), or the bodies of the selected faces, become the cells",
|
selection::Target::Bodies => "The selected bodies (Select: Bodies), or the bodies of the selected faces, become the cells",
|
||||||
|
selection::Target::ExtrudeFaces => "Pick the source face and use it, then pick the target surface and use it",
|
||||||
};
|
};
|
||||||
use_selection = ui.add_enabled(selected.is_some(), egui::Button::new("Use selection")).on_hover_text(tip).clicked();
|
let button = match target {
|
||||||
|
selection::Target::ExtrudeFaces if selection::extrude_key(¶ms) == "source" => "Use as source",
|
||||||
|
selection::Target::ExtrudeFaces => "Use as target surface",
|
||||||
|
_ => "Use selection",
|
||||||
|
};
|
||||||
|
use_selection = ui.add_enabled(selected.is_some(), egui::Button::new(button)).on_hover_text(tip).clicked();
|
||||||
match &selected {
|
match &selected {
|
||||||
Some(text) => ui.add(egui::Label::new(RichText::new(text).color(t.pick_selected)).wrap()),
|
Some(text) => ui.add(egui::Label::new(RichText::new(text).color(t.pick_selected)).wrap()),
|
||||||
None => ui.add(egui::Label::new(RichText::new("Pick in the viewport, or choose below").color(t.text_dim)).wrap()),
|
None => ui.add(egui::Label::new(RichText::new("Pick in the viewport, or choose below").color(t.text_dim)).wrap()),
|
||||||
|
|||||||
@@ -1723,6 +1723,8 @@ impl ModelApp {
|
|||||||
}
|
}
|
||||||
Value::Object(out)
|
Value::Object(out)
|
||||||
}
|
}
|
||||||
|
selection::Target::ExtrudeFaces => selection::with_extrude_faces(¶ms, &self.pick.selection)
|
||||||
|
.ok_or("no face is selected: pick the source face, then the target surface (Select: Faces)")?,
|
||||||
selection::Target::Bodies => {
|
selection::Target::Bodies => {
|
||||||
let bodies = selection::bodies(&self.pick.selection).ok_or("no geometry is selected: pick cells with Select: Bodies")?;
|
let bodies = selection::bodies(&self.pick.selection).ok_or("no geometry is selected: pick cells with Select: Bodies")?;
|
||||||
let mut out = params.as_object().cloned().unwrap_or_default();
|
let mut out = params.as_object().cloned().unwrap_or_default();
|
||||||
|
|||||||
@@ -852,6 +852,9 @@ pub enum Target {
|
|||||||
/// The selected bodies (or the bodies of the selected faces, edges and vertices) as the
|
/// The selected bodies (or the bodies of the selected faces, edges and vertices) as the
|
||||||
/// `bodies` list (the cells of a partitioned mesh).
|
/// `bodies` list (the cells of a partitioned mesh).
|
||||||
Bodies,
|
Bodies,
|
||||||
|
/// The selected faces as an extrusion's `source` while it has none, then as its
|
||||||
|
/// `to_surface`: pick the source face, then the target surface.
|
||||||
|
ExtrudeFaces,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where command `id`'s form takes the selection, if it does. `params` (the form's current
|
/// Where command `id`'s form takes the selection, if it does. `params` (the form's current
|
||||||
@@ -863,6 +866,7 @@ pub fn target(id: &str, params: &Value) -> Option<Target> {
|
|||||||
"model.reference_point.create" | "model.reference_point.edit" => Some(Target::Position),
|
"model.reference_point.create" | "model.reference_point.edit" => Some(Target::Position),
|
||||||
"geometry.datum.point" | "geometry.datum.point.edit" => Some(Target::DatumPoint),
|
"geometry.datum.point" | "geometry.datum.point.edit" => Some(Target::DatumPoint),
|
||||||
"mesh.cells" => Some(Target::Bodies),
|
"mesh.cells" => Some(Target::Bodies),
|
||||||
|
"mesh.hex.extrude" => Some(Target::ExtrudeFaces),
|
||||||
"model.boundary_condition.create" | "model.boundary_condition.edit" => Some(Target::TargetSet),
|
"model.boundary_condition.create" | "model.boundary_condition.edit" => Some(Target::TargetSet),
|
||||||
"model.section.create" | "model.section.edit" | "model.coupling.create" | "model.coupling.edit" | "interaction.rigid_wall" => {
|
"model.section.create" | "model.section.edit" | "model.coupling.create" | "model.coupling.edit" | "interaction.rigid_wall" => {
|
||||||
Some(Target::Set)
|
Some(Target::Set)
|
||||||
@@ -878,8 +882,10 @@ pub fn target(id: &str, params: &Value) -> Option<Target> {
|
|||||||
/// The filter command `id`'s form picks with, when its target is one kind of entity: edges for
|
/// The filter command `id`'s form picks with, when its target is one kind of entity: edges for
|
||||||
/// edge seeds, and the vertex, edge or face a datum point is defined on.
|
/// edge seeds, and the vertex, edge or face a datum point is defined on.
|
||||||
pub fn form_filter(id: &str, params: &Value) -> Option<Filter> {
|
pub fn form_filter(id: &str, params: &Value) -> Option<Filter> {
|
||||||
if target(id, params) == Some(Target::Edges) {
|
match target(id, params) {
|
||||||
return Some(Filter::Edges);
|
Some(Target::Edges) => return Some(Filter::Edges),
|
||||||
|
Some(Target::ExtrudeFaces) => return Some(Filter::Faces),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
match id {
|
match id {
|
||||||
"geometry.datum.point" | "geometry.datum.point.edit" => match params.pointer("/by/type").and_then(Value::as_str) {
|
"geometry.datum.point" | "geometry.datum.point.edit" => match params.pointer("/by/type").and_then(Value::as_str) {
|
||||||
@@ -923,6 +929,36 @@ pub fn with_edges(params: &Value, selection: &Selection) -> Option<Value> {
|
|||||||
Some(Value::Object(out))
|
Some(Value::Object(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The key of an extrusion's form the selected faces go into: `source` while it names no face
|
||||||
|
/// (and no `source_set`), then `to_surface`.
|
||||||
|
pub fn extrude_key(params: &Value) -> &'static str {
|
||||||
|
let named = |k: &str| {
|
||||||
|
params
|
||||||
|
.get(k)
|
||||||
|
.is_some_and(|v| v.as_array().is_some_and(|a| a.iter().any(|f| f.get("face").and_then(Value::as_str).is_some_and(|n| !n.is_empty()))))
|
||||||
|
};
|
||||||
|
if named("source") || params.get("source_set").and_then(Value::as_str).is_some_and(|s| !s.is_empty()) { "to_surface" } else { "source" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `params` with the selection's faces as an extrusion's source or target surface
|
||||||
|
/// ([`extrude_key`]); `None` when no face is selected.
|
||||||
|
pub fn with_extrude_faces(params: &Value, selection: &Selection) -> Option<Value> {
|
||||||
|
let faces: Vec<Value> = selection
|
||||||
|
.items()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|i| match i {
|
||||||
|
Item::Geometry(g) if matches!(g.entity, Entity::Face(_)) => Some(json!(g)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if faces.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut out = params.as_object().cloned().unwrap_or_default();
|
||||||
|
out.insert(extrude_key(params).into(), Value::Array(faces));
|
||||||
|
Some(Value::Object(out))
|
||||||
|
}
|
||||||
|
|
||||||
/// `params` with the set `set` as the target.
|
/// `params` with the set `set` as the target.
|
||||||
pub fn with_set(params: &Value, target: Target, set: &str) -> Value {
|
pub fn with_set(params: &Value, target: Target, set: &str) -> Value {
|
||||||
let mut out = params.as_object().cloned().unwrap_or_default();
|
let mut out = params.as_object().cloned().unwrap_or_default();
|
||||||
@@ -933,7 +969,7 @@ pub fn with_set(params: &Value, target: Target, set: &str) -> Value {
|
|||||||
Target::Set => {
|
Target::Set => {
|
||||||
out.insert("set".into(), json!(set));
|
out.insert("set".into(), json!(set));
|
||||||
}
|
}
|
||||||
Target::Members | Target::Edges | Target::Position | Target::DatumPoint | Target::Bodies => {}
|
Target::Members | Target::Edges | Target::Position | Target::DatumPoint | Target::Bodies | Target::ExtrudeFaces => {}
|
||||||
}
|
}
|
||||||
Value::Object(out)
|
Value::Object(out)
|
||||||
}
|
}
|
||||||
@@ -1237,6 +1273,26 @@ mod tests {
|
|||||||
assert_eq!(p.overlay(&sel, None, sel_c, hov_c).tris.len(), s.display.tris.len());
|
assert_eq!(p.overlay(&sel, None, sel_c, hov_c).tris.len(), s.display.tris.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An extrusion's form takes the source face first, then the target surface (#400).
|
||||||
|
#[test]
|
||||||
|
fn an_extrusion_takes_its_source_then_its_target_surface() {
|
||||||
|
assert_eq!(target("mesh.hex.extrude", &json!({})), Some(Target::ExtrudeFaces));
|
||||||
|
assert_eq!(form_filter("mesh.hex.extrude", &json!({})), Some(Filter::Faces));
|
||||||
|
let mut s = Selection::default();
|
||||||
|
s.apply([Item::Geometry(GeometryRef::face("Post", "F1:-z").unwrap()), Item::Node(4)], Mode::Replace);
|
||||||
|
let form = with_extrude_faces(&json!({"layers": 4, "source": [{"body": "", "face": ""}]}), &s).unwrap();
|
||||||
|
assert_eq!(form, json!({"layers": 4, "source": [{"body": "Post", "face": "F1:-z"}]}));
|
||||||
|
s.apply([Item::Geometry(GeometryRef::face("Wall", "F7").unwrap())], Mode::Replace);
|
||||||
|
let form = with_extrude_faces(&form, &s).unwrap();
|
||||||
|
assert_eq!(form["to_surface"], json!([{"body": "Wall", "face": "F7"}]));
|
||||||
|
assert_eq!(form["source"], json!([{"body": "Post", "face": "F1:-z"}]), "the source stays");
|
||||||
|
// Picking again replaces the target surface; a source set counts as a source.
|
||||||
|
assert_eq!(extrude_key(&form), "to_surface");
|
||||||
|
assert_eq!(extrude_key(&json!({"source_set": "Section"})), "to_surface");
|
||||||
|
s.apply([Item::Node(4)], Mode::Replace);
|
||||||
|
assert!(with_extrude_faces(&form, &s).is_none(), "no face selected");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn selections_add_remove_toggle_and_cap() {
|
fn selections_add_remove_toggle_and_cap() {
|
||||||
let mut s = Selection::default();
|
let mut s = Selection::default();
|
||||||
|
|||||||
Reference in new issue
Block a user