forked from 1b-agent/fieldcraft-upstream
Engine: geometry.bodies lists each body's named edges
Each edge with its persistent name and length. A reference to an edge name selects its numbered pieces (`~k`), as a face name selects its `#k` pieces. Part of #230. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
a11b971dae
commit
ffb71de6e1
5 files changed
+21
-5
No files matched your search
@@ -25,7 +25,7 @@ pub const OPEN: CommandSpec = CommandSpec {
|
|||||||
pub const BODIES: CommandSpec = CommandSpec {
|
pub const BODIES: CommandSpec = CommandSpec {
|
||||||
id: "geometry.bodies",
|
id: "geometry.bodies",
|
||||||
label: "Bodies",
|
label: "Bodies",
|
||||||
params: "{} — the open design's bodies: measures and persistent face names (by face index)",
|
params: "{} — the open design's bodies: measures, persistent face names (by face index) and edges (persistent name `<face>|<face>`, `~k` when several separate the same faces, and length)",
|
||||||
form: Some(&[]),
|
form: Some(&[]),
|
||||||
kind: CommandKind::Model,
|
kind: CommandKind::Model,
|
||||||
requires: &[], // No design is an answer too: no bodies.
|
requires: &[], // No design is an answer too: no bodies.
|
||||||
@@ -126,6 +126,7 @@ fn bodies(s: &mut Session, _: &Value) -> Result<Value> {
|
|||||||
let mut out = Vec::with_capacity(design.bodies.len());
|
let mut out = Vec::with_capacity(design.bodies.len());
|
||||||
for b in &design.bodies {
|
for b in &design.bodies {
|
||||||
let m = b.measure()?;
|
let m = b.measure()?;
|
||||||
|
let edges: Vec<Value> = b.edges()?.iter().map(|e| json!({"name": e.name, "length": e.length()})).collect();
|
||||||
out.push(json!({
|
out.push(json!({
|
||||||
"name": b.name,
|
"name": b.name,
|
||||||
"volume": m.volume,
|
"volume": m.volume,
|
||||||
@@ -133,6 +134,7 @@ fn bodies(s: &mut Session, _: &Value) -> Result<Value> {
|
|||||||
"centroid": m.centroid,
|
"centroid": m.centroid,
|
||||||
"bbox": [m.bbox_min, m.bbox_max],
|
"bbox": [m.bbox_min, m.bbox_max],
|
||||||
"faces": b.faces().iter().map(|f| json!({"name": f.name, "exact": f.exact})).collect::<Vec<_>>(),
|
"faces": b.faces().iter().map(|f| json!({"name": f.name, "exact": f.exact})).collect::<Vec<_>>(),
|
||||||
|
"edges": edges,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Ok(Value::Array(out))
|
Ok(Value::Array(out))
|
||||||
|
|||||||
@@ -645,6 +645,10 @@ mod tests {
|
|||||||
assert!((volume - expect).abs() < 1e-3 * expect, "{volume}");
|
assert!((volume - expect).abs() < 1e-3 * expect, "{volume}");
|
||||||
let faces = bodies[0]["faces"].as_array().unwrap();
|
let faces = bodies[0]["faces"].as_array().unwrap();
|
||||||
assert_eq!(faces.len(), 8);
|
assert_eq!(faces.len(), 8);
|
||||||
|
let edges = bodies[0]["edges"].as_array().unwrap();
|
||||||
|
assert_eq!(edges.len(), 18);
|
||||||
|
// Through the thickness: four box corners, the two hole seams.
|
||||||
|
assert_eq!(edges.iter().filter(|e| (e["length"].as_f64().unwrap() - 10.0).abs() < 1e-6).count(), 6);
|
||||||
|
|
||||||
let t = s.execute("geometry.tessellate", &json!({"body": "Body1", "tolerance": 0.05})).unwrap();
|
let t = s.execute("geometry.tessellate", &json!({"body": "Body1", "tolerance": 0.05})).unwrap();
|
||||||
let per_face: Vec<u64> = t["faces"].as_array().unwrap().iter().map(|f| f["triangles"].as_u64().unwrap()).collect();
|
let per_face: Vec<u64> = t["faces"].as_array().unwrap().iter().map(|f| f["triangles"].as_u64().unwrap()).collect();
|
||||||
@@ -662,6 +666,8 @@ mod tests {
|
|||||||
let wider = s.execute("geometry.bodies", &json!({})).unwrap();
|
let wider = s.execute("geometry.bodies", &json!({})).unwrap();
|
||||||
assert!(wider[0]["volume"].as_f64().unwrap() > volume);
|
assert!(wider[0]["volume"].as_f64().unwrap() > volume);
|
||||||
assert_eq!(wider[0]["faces"], bodies[0]["faces"]);
|
assert_eq!(wider[0]["faces"], bodies[0]["faces"]);
|
||||||
|
let names = |b: &Value| b[0]["edges"].as_array().unwrap().iter().map(|e| e["name"].clone()).collect::<Vec<_>>();
|
||||||
|
assert_eq!(names(&wider), names(&bodies));
|
||||||
assert_eq!(s.journal.len(), 2);
|
assert_eq!(s.journal.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ pub fn tool_definitions() -> Vec<Value> {
|
|||||||
"Inspect the model",
|
"Inspect the model",
|
||||||
"The model: node and element counts, the unit system and the definitions by kind (model.info); the step \
|
"The model: node and element counts, the unit system and the definitions by kind (model.info); the step \
|
||||||
table (which boundary conditions and loads each step has, model.activity); the open design's bodies with \
|
table (which boundary conditions and loads each step has, model.activity); the open design's bodies with \
|
||||||
their measures and persistent face names (geometry.bodies), which sets name as {body, face}.",
|
their measures, persistent face names (geometry.bodies), which sets name as {body, face}, and persistent \
|
||||||
|
edge names `<face>|<face>` with lengths, which sets name as {body, edge}.",
|
||||||
obj(json!({}), &[]),
|
obj(json!({}), &[]),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -140,17 +140,20 @@ impl GeometryRef {
|
|||||||
/// Whether this reference (from a set) selects the tagged entity `tag`: the same body and
|
/// Whether this reference (from a set) selects the tagged entity `tag`: the same body and
|
||||||
/// kind, and the same name or the name of one of its pieces. A B-rep face the kernel splits
|
/// kind, and the same name or the name of one of its pieces. A B-rep face the kernel splits
|
||||||
/// into pieces (a full cylinder is two half-cylinders) carries the face's name with `#0`,
|
/// into pieces (a full cylinder is two half-cylinders) carries the face's name with `#0`,
|
||||||
/// `#1`, … appended; a reference to the plain name selects all of them.
|
/// `#1`, … appended; a reference to the plain name selects all of them. Edges separating the
|
||||||
|
/// same faces are numbered `~0`, `~1`, … the same way.
|
||||||
pub fn selects(&self, tag: &GeometryRef) -> bool {
|
pub fn selects(&self, tag: &GeometryRef) -> bool {
|
||||||
if self.body != tag.body || self.entity.kind() != tag.entity.kind() {
|
if self.body != tag.body || self.entity.kind() != tag.entity.kind() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
match (self.entity.name(), tag.entity.name()) {
|
match (self.entity.name(), tag.entity.name()) {
|
||||||
(Some(want), Some(have)) => {
|
(Some(want), Some(have)) => {
|
||||||
|
// A face's pieces are numbered `#k`, an edge's `~k` (edges separating the same faces).
|
||||||
|
let mark = if matches!(self.entity, Entity::Edge(_)) { '~' } else { '#' };
|
||||||
have == want
|
have == want
|
||||||
|| have
|
|| have
|
||||||
.strip_prefix(want)
|
.strip_prefix(want)
|
||||||
.and_then(|r| r.strip_prefix('#'))
|
.and_then(|r| r.strip_prefix(mark))
|
||||||
.is_some_and(|k| !k.is_empty() && k.bytes().all(|b| b.is_ascii_digit()))
|
.is_some_and(|k| !k.is_empty() && k.bytes().all(|b| b.is_ascii_digit()))
|
||||||
}
|
}
|
||||||
_ => true,
|
_ => true,
|
||||||
@@ -354,6 +357,10 @@ mod tests {
|
|||||||
assert!(!r.selects(&GeometryRef::face("Body2", "F2:side").unwrap()));
|
assert!(!r.selects(&GeometryRef::face("Body2", "F2:side").unwrap()));
|
||||||
assert!(!r.selects(&GeometryRef::new("Body1", Entity::Edge("F2:side".into())).unwrap()));
|
assert!(!r.selects(&GeometryRef::new("Body1", Entity::Edge("F2:side".into())).unwrap()));
|
||||||
assert!(GeometryRef::body("Body1").unwrap().selects(&GeometryRef::body("Body1").unwrap()));
|
assert!(GeometryRef::body("Body1").unwrap().selects(&GeometryRef::body("Body1").unwrap()));
|
||||||
|
let edge = |n: &str| GeometryRef::new("Body1", Entity::Edge(n.into())).unwrap();
|
||||||
|
assert!(edge("A|B").selects(&edge("A|B~1")));
|
||||||
|
assert!(!edge("A|B").selects(&edge("A|B#1")));
|
||||||
|
assert!(!edge("A|B~0").selects(&edge("A|B~1")));
|
||||||
|
|
||||||
for bad in [
|
for bad in [
|
||||||
serde_json::json!({"body": ""}),
|
serde_json::json!({"body": ""}),
|
||||||
|
|||||||
+1
-1
@@ -94,7 +94,7 @@ software on the computer.
|
|||||||
| `list_commands` | `filter?` | Every command with its label, parameter documentation and `schema` (its parameters as JSON Schema: types, units as `x-dimension`, defaults, required fields, one-of variants) and `requires` (what the session must hold first: `design`, `geometry`, `mesh`, `material`, `step`, `result`, …); `filter` matches id and label. |
|
| `list_commands` | `filter?` | Every command with its label, parameter documentation and `schema` (its parameters as JSON Schema: types, units as `x-dimension`, defaults, required fields, one-of variants) and `requires` (what the session must hold first: `design`, `geometry`, `mesh`, `material`, `step`, `result`, …); `filter` matches id and label. |
|
||||||
| `execute` | `command`, `params?` | Run any command (`list_commands`). A failure leaves the model unchanged and names what to fix; a command whose `requires` are not met fails before doing anything, naming each missing piece and the command that provides it. |
|
| `execute` | `command`, `params?` | Run any command (`list_commands`). A failure leaves the model unchanged and names what to fix; a command whose `requires` are not met fails before doing anything, naming each missing piece and the command that provides it. |
|
||||||
| `batch` | `commands: [{command, params}]` | Run steps in order, stop at the first error; the reply has every result and the failing step (`failed.index`, `failed.command`, `failed.error`). Steps before it stay applied (`undo` takes them back). |
|
| `batch` | `commands: [{command, params}]` | Run steps in order, stop at the first error; the reply has every result and the failing step (`failed.index`, `failed.command`, `failed.error`). Steps before it stay applied (`undo` takes them back). |
|
||||||
| `inspect_model` | — | `model.info` (counts, units, definitions), `model.activity` (the step table) and `geometry.bodies` (bodies with measures and persistent face names, which sets name as `{body, face}`). |
|
| `inspect_model` | — | `model.info` (counts, units, definitions), `model.activity` (the step table) and `geometry.bodies` (bodies with measures, persistent face names, which sets name as `{body, face}`, and persistent edge names `<face>|<face>` with lengths, which sets name as `{body, edge}`). |
|
||||||
| `check_model` | — | `model.check`: errors and warnings, each naming its item. |
|
| `check_model` | — | `model.check`: errors and warnings, each naming its item. |
|
||||||
| `open` | `path`, `model?` | By extension: `.fieldcraft` (`file.open`), `.solidcraft` (`geometry.open`), `.3mf` (`geometry.import.mesh`). For a design, `model` is `keep` (default: the mesh and definitions stay, as a reopen after an edit wants) or `new` (a new model with the design); the reply's `related` is false when the design has none of the bodies the model refers to, and `stale` lists the mesh bodies and sets that no longer resolve. |
|
| `open` | `path`, `model?` | By extension: `.fieldcraft` (`file.open`), `.solidcraft` (`geometry.open`), `.3mf` (`geometry.import.mesh`). For a design, `model` is `keep` (default: the mesh and definitions stay, as a reopen after an edit wants) or `new` (a new model with the design); the reply's `related` is false when the design has none of the bodies the model refers to, and `stale` lists the mesh bodies and sets that no longer resolve. |
|
||||||
| `save` | `path?` | `file.save`: to `path` or the model's own file. |
|
| `save` | `path?` | `file.save`: to `path` or the model's own file. |
|
||||||
|
|||||||
Reference in new issue
Block a user