forked from 1b-agent/fieldcraft-upstream
mesh: geometry.features, mesh.plan and mesh.plan.apply from the feature graph (#402)
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
8dd7446569
commit
efe90ec50e
12 files changed
+1449
-4
No files matched your search
@@ -1599,6 +1599,15 @@ pub const PARTITION: &[Field] = &[
|
||||
references("bodies", "Bodies", C::Body).help("The bodies to divide; every body the plane cuts when empty.").hint("every body the plane divides"),
|
||||
];
|
||||
pub const PARTITION_DELETE: &[Field] = existing!("Partition", C::Partition);
|
||||
pub const MESH_PLAN: &[Field] = &[
|
||||
reference("body", "Body", C::Body).help("The body of the design to plan (as opened, not a partition cell).").req(),
|
||||
text("name", "Name").help("The name the plan is saved under.").hint("Plan-<body>"),
|
||||
positive("size", "Element size", D::Millimetre).help("The largest element size; thin walls make it smaller.").hint("1/25 of the diagonal"),
|
||||
int("layers", "Layers through walls", 1, 10).help("Elements through the thinnest wall the features give (a shell, a plate).").hint("2"),
|
||||
int("max_elements", "Most elements", 1, 5_000_000).help("The size grows past the walls' when more elements would be needed.").hint("300000"),
|
||||
];
|
||||
pub const MESH_PLAN_APPLY: &[Field] = &[text("plan", "Plan").help("The name of a saved mesh plan (mesh.plan).").req()];
|
||||
pub const FEATURES: &[Field] = &[reference("body", "Body", C::Body).help("Only this body's features.").hint("every body")];
|
||||
pub const TESSELLATE: &[Field] = &[reference("body", "Body", C::Body).help("The body to tessellate.").req(), TOLERANCE];
|
||||
pub const IMPORT_MESH: &[Field] = &[path("path", "3MF file", false).help("A 3MF surface mesh with named faces.").req()];
|
||||
pub const FILE_OPEN: &[Field] = &[path("path", "Model", false).help("The FieldCraft model file to open.").req()];
|
||||
|
||||
@@ -33,6 +33,16 @@ pub const BODIES: CommandSpec = CommandSpec {
|
||||
run: bodies,
|
||||
};
|
||||
|
||||
pub const FEATURES: CommandSpec = CommandSpec {
|
||||
id: "geometry.features",
|
||||
label: "Feature Graph",
|
||||
params: r#"{"body"?: "Base" (default: every body)} — the features of the SolidCraft timeline that built each body of the open design, in order (read-only): each feature's `id` (`F<id>` in persistent names), `name`, `type` (extrude, box, cylinder, hole, shell, fillet, combine, pattern, mirror, split, …), `operation` (new_body, join, cut, intersect), `values` (its numeric inputs evaluated: mm, radians, counts), `placement` (a primitive's corner, base or centre), `direction` (an extrude's along the sketch normal, a cylinder's axis, a hole's drilling direction), `two_sided`, `sketch` (plane origin, axes and normal, and the profiles used: lines, arcs and circles in sketch coordinates, holes, curve ids), `transforms` (pattern and mirror copies, 4×4 row-major), `targets` and `tools`, the stored `definition`, the persistent `faces` and `edges` of the body it made, and `error` when it failed to rebuild. A partition cell is described by the body it was cut from"#,
|
||||
form: Some(super::forms::FEATURES),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[Requirement::Design],
|
||||
run: features,
|
||||
};
|
||||
|
||||
pub const CENTRE: CommandSpec = CommandSpec {
|
||||
id: "geometry.centre",
|
||||
label: "Centre of Selection",
|
||||
@@ -170,6 +180,16 @@ fn bodies(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
Ok(Value::Array(out))
|
||||
}
|
||||
|
||||
fn features(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let body = match p.get("body") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(v) => Some(v.as_str().ok_or_else(|| EngineError::param("body", ProblemKind::NotAName))?),
|
||||
};
|
||||
let design = s.design.as_deref().ok_or_else(|| EngineError::Params("open a design first".into()))?;
|
||||
let graph = design.features(body)?;
|
||||
Ok(json!({"bodies": graph}))
|
||||
}
|
||||
|
||||
/// Most geometry items and nodes `geometry.centre` takes.
|
||||
const MAX_CENTRE_ITEMS: usize = 10_000;
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ mod mirror;
|
||||
mod model;
|
||||
pub(crate) mod nodes;
|
||||
pub(crate) mod octree;
|
||||
pub(crate) mod plan;
|
||||
mod project;
|
||||
pub(crate) mod relax;
|
||||
pub mod replace;
|
||||
@@ -47,6 +48,7 @@ pub static ALL: &[CommandSpec] = &[
|
||||
model::INFO,
|
||||
geometry::OPEN,
|
||||
geometry::BODIES,
|
||||
geometry::FEATURES,
|
||||
geometry::CENTRE,
|
||||
geometry::TESSELLATE,
|
||||
geometry::IMPORT_MESH,
|
||||
@@ -83,6 +85,8 @@ pub static ALL: &[CommandSpec] = &[
|
||||
hex::HEX_BLOCKS,
|
||||
project::HEX_PROJECT,
|
||||
cells::CELLS,
|
||||
plan::PLAN,
|
||||
plan::APPLY,
|
||||
seeds::SEED_EDGE,
|
||||
seeds::SEED_CLEAR,
|
||||
seeds::SEED_LIST,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -52,6 +52,7 @@ const REMESH: &[&str] = &[
|
||||
"mesh.hex.sweep",
|
||||
"mesh.hex.mapped",
|
||||
"mesh.hex.blocks",
|
||||
"mesh.plan.apply",
|
||||
"mesh.hex.octree",
|
||||
"mesh.cells",
|
||||
"mesh.block",
|
||||
|
||||
@@ -130,6 +130,7 @@ pub static MESHERS: &[(&str, Targets)] = &[
|
||||
("mesh.hex.blocks", listed_or_all),
|
||||
("mesh.hex.project", body_or_all),
|
||||
("mesh.cells", listed_or_all),
|
||||
("mesh.plan.apply", super::plan::plan_bodies),
|
||||
("mesh.planar", super::mesh::planar_body),
|
||||
("mesh.mirror", super::mirror::image_names),
|
||||
];
|
||||
@@ -173,7 +174,7 @@ fn made_kind(command: &str, params: &Value) -> Option<ElementKind> {
|
||||
Some(if params.get("order").and_then(Value::as_str) == Some("quadratic") { ElementKind::Tet10 } else { ElementKind::Tet4 })
|
||||
}
|
||||
"mesh.hex.project" => Some(ElementKind::Hex8),
|
||||
"mesh.hex.generate" | "mesh.hex.sweep" | "mesh.hex.mapped" | "mesh.hex.blocks" => {
|
||||
"mesh.hex.generate" | "mesh.hex.sweep" | "mesh.hex.mapped" | "mesh.hex.blocks" | "mesh.plan.apply" => {
|
||||
Some(if params.get("order").and_then(Value::as_i64) == Some(2) { ElementKind::Hex20 } else { ElementKind::Hex8 })
|
||||
}
|
||||
_ => None,
|
||||
@@ -313,6 +314,13 @@ mod tests {
|
||||
("mesh.hex.blocks", vec![tet_other.clone()], json!({"bodies": ["Box.1"], "size": 2.5}), "Box.1", "Box.2"),
|
||||
("mesh.hex.project", vec![tet_other.clone()], json!({"body": "Box.1", "size": 2.5, "merge": 0}), "Box.1", "Box.2"),
|
||||
("mesh.cells", vec![tet_other.clone()], json!({"bodies": ["Box.1"], "size": 2.5, "default": "hex"}), "Box.1", "Box.2"),
|
||||
(
|
||||
"mesh.plan.apply",
|
||||
vec![tet_other.clone(), json!({"command": "mesh.plan", "params": {"body": "Box.1", "size": 2.5}})],
|
||||
json!({"plan": "Plan-Box_1"}),
|
||||
"Box.1",
|
||||
"Box.2",
|
||||
),
|
||||
("mesh.planar", vec![tet_other.clone()], json!({"polygon": square, "size": 2.5, "name": "Box.1"}), "Box.1", "Box.2"),
|
||||
// The image's tets (meshed before) are replaced by the mirrored hexahedra.
|
||||
(
|
||||
|
||||
Reference in new issue
Block a user