108 lines
3.5 KiB
Rust
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})")
|
|
}
|