// 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 = 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})") }