bimr-engine/eval/src/nodes/geometry/extrusion.rs
2026-09-01 15:32:49 +02:00

108 lines
3.5 KiB
Rust

// SPDX-FileCopyrightText: 2026 Milovann Yanatchkov
// SPDX-License-Identifier: MIT
//! Extrusion — the whole engine story of `Extrusion`.
//!
//! Nature lives in the kernel (`bimr::geometry::Extrusion`): the profile by
//! value (E1 resolved — no handle), the sweep vector; `entity.mesh()` builds
//! the side surface standalone. The engine keeps graph edges to the profile
//! source and the vector in `refs`.
//!
//! The profile comes from a List of Lines or a `Cut` record (its created
//! pieces); the sweep from a `Vector` node.
use bimr::Entity;
use bimr::geometry::Extrusion as KernelExtrusion;
use bimr::geometry::Line;
use parser::ast::Argument;
use crate::error::Error;
use crate::nodes::{lookup, ref_name};
use crate::store::Store;
use crate::value::Node;
/// IFCX path prefix — the per-type counter lives with the emitter.
pub(crate) const IFCX_PREFIX: &str = "extrusion";
/// `e = Extrusion(profile, vec)` — positional only. The profile is a List of
/// Lines (kind `"Line"`) or a `Cut` record; the sweep a `Vector`.
pub(crate) fn eval(
store: &mut Store,
file: &str,
name: &str,
args: &[Argument],
) -> Result<(), Error> {
let positional = crate::nodes::positional_args(args)?;
if positional.len() != 2 {
return Err(Error::Unsupported("arity"));
}
let profile_name = crate::nodes::name_of(positional[0])?;
let vec_name = crate::nodes::name_of(positional[1])?;
let profile_node = lookup(store, &profile_name)?;
let profile: Vec<Line> = match &profile_node.entity {
Entity::List(list) => {
if list.kind() != "Line" {
return Err(Error::ListKind {
name: profile_name,
expected: "Line",
got: list.kind(),
});
}
list.items()
.iter()
.map(|item| match item {
Entity::Line(l) => *l,
other => unreachable!("kind `{}` checked: {}", list.kind(), other.type_name()),
})
.collect()
}
Entity::Cut(_) => store
.outputs_of(profile_node.guid)
.iter()
.map(|&guid| {
let node = store.resolve(guid).expect("indexed");
match node.entity {
Entity::Line(l) => l,
ref other => unreachable!("cut pieces are Lines: {}", other.type_name()),
}
})
.collect(),
other => {
return Err(Error::WrongNature {
name: profile_name,
expected: "List",
got: other.type_name(),
});
}
};
let vec_node = lookup(store, &vec_name)?;
let vec = match &vec_node.entity {
Entity::Vector(v) => v.to_vec3(),
other => {
return Err(Error::WrongNature {
name: vec_name,
expected: "Vector",
got: other.type_name(),
});
}
};
let extrusion = KernelExtrusion::new(profile, vec);
store.insert(
file,
name,
Entity::Extrusion(extrusion),
vec![profile_node.guid, vec_node.guid],
);
Ok(())
}
/// The compact BIMR text: `e1 = Extrusion(ct1, v1)`.
pub(crate) fn to_bimr(store: &Store, node: &Node, _e: &KernelExtrusion) -> String {
let name = ref_name(store, node.guid);
let profile_var = ref_name(store, node.refs[0]);
let vec_var = ref_name(store, node.refs[1]);
format!("{name} = Extrusion({profile_var}, {vec_var})")
}