use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::error::GeometryError;
use crate::surface::bounded::{plane_slot, surface_slot, trimmed_slot};
use crate::surface::bspline::slot as bspline_slot;
use crate::surface::elementary::slot as elementary_slot;
use super::std_profile::positive;
use super::{invalid, refs, require_finite};
pub fn spherical_surface(
tx: &mut Transaction,
position: EntityId,
radius: f64,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCSPHERICALSURFACE";
positive(T, "Radius", radius)?;
let mut attrs = vec![Value::Null; 2];
attrs[elementary_slot::POSITION] = Value::Ref(position);
attrs[elementary_slot::RADIUS] = Value::Real(radius);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn toroidal_surface(
tx: &mut Transaction,
position: EntityId,
major_radius: f64,
minor_radius: f64,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCTOROIDALSURFACE";
positive(T, "MajorRadius", major_radius)?;
positive(T, "MinorRadius", minor_radius)?;
let mut attrs = vec![Value::Null; 3];
attrs[elementary_slot::POSITION] = Value::Ref(position);
attrs[elementary_slot::MAJOR_RADIUS] = Value::Real(major_radius);
attrs[elementary_slot::MINOR_RADIUS] = Value::Real(minor_radius);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn curve_bounded_plane(
tx: &mut Transaction,
basis: EntityId,
outer_boundary: EntityId,
inner_boundaries: &[EntityId],
) -> EntityId {
let mut attrs = vec![Value::Null; 3];
attrs[plane_slot::BASIS_SURFACE] = Value::Ref(basis);
attrs[plane_slot::OUTER_BOUNDARY] = Value::Ref(outer_boundary);
attrs[plane_slot::INNER_BOUNDARIES] = refs(inner_boundaries);
tx.create(Entity::new("IFCCURVEBOUNDEDPLANE", attrs))
}
pub fn curve_bounded_surface(
tx: &mut Transaction,
basis: EntityId,
boundaries: &[EntityId],
implicit_outer: bool,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCCURVEBOUNDEDSURFACE";
if boundaries.is_empty() {
return Err(invalid(T, "Boundaries", "expected at least one boundary"));
}
let mut attrs = vec![Value::Null; 3];
attrs[surface_slot::BASIS_SURFACE] = Value::Ref(basis);
attrs[surface_slot::BOUNDARIES] = refs(boundaries);
attrs[surface_slot::IMPLICIT_OUTER] = Value::Bool(implicit_outer);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn rectangular_trimmed_surface(
tx: &mut Transaction,
basis: EntityId,
u: (f64, f64),
v: (f64, f64),
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCRECTANGULARTRIMMEDSURFACE";
let (u1, u2) = u;
let (v1, v2) = v;
require_finite(T, "U1", &[u1, u2])?;
require_finite(T, "V1", &[v1, v2])?;
if u1 == u2 {
return Err(invalid(T, "U1", "U1 and U2 must differ"));
}
if v1 == v2 {
return Err(invalid(T, "V1", "V1 and V2 must differ"));
}
let mut attrs = vec![Value::Null; 7];
attrs[trimmed_slot::BASIS_SURFACE] = Value::Ref(basis);
attrs[trimmed_slot::U1] = parameter(u1);
attrs[trimmed_slot::V1] = parameter(v1);
attrs[trimmed_slot::U2] = parameter(u2);
attrs[trimmed_slot::V2] = parameter(v2);
attrs[trimmed_slot::USENSE] = Value::Bool(u2 > u1);
attrs[trimmed_slot::VSENSE] = Value::Bool(v2 > v1);
Ok(tx.create(Entity::new(T, attrs)))
}
fn parameter(value: f64) -> Value {
Value::Real(value)
}
#[derive(Debug, Clone, Copy)]
pub struct SurfaceKnots<'a> {
pub multiplicities: &'a [i64],
pub knots: &'a [f64],
}
#[derive(Debug, Clone, Copy)]
pub struct SurfaceBasis<'a> {
pub degree: (i64, i64),
pub u: SurfaceKnots<'a>,
pub v: SurfaceKnots<'a>,
pub form: &'a str,
pub knot_spec: &'a str,
}
pub fn bspline_surface_with_knots(
tx: &mut Transaction,
control_points: &[&[EntityId]],
basis: SurfaceBasis<'_>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCBSPLINESURFACEWITHKNOTS";
let attrs = surface_attrs(T, control_points, basis)?;
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn rational_bspline_surface_with_knots(
tx: &mut Transaction,
control_points: &[&[EntityId]],
basis: SurfaceBasis<'_>,
weights: &[&[f64]],
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCRATIONALBSPLINESURFACEWITHKNOTS";
if weights.len() != control_points.len() {
return Err(invalid(
T,
"WeightsData",
format!(
"{} weight rows for {} control point rows",
weights.len(),
control_points.len()
),
));
}
for (index, (row, points)) in weights.iter().zip(control_points).enumerate() {
if row.len() != points.len() {
return Err(invalid(
T,
"WeightsData",
format!(
"weight row {index} has {} entries, its control row has {}",
row.len(),
points.len()
),
));
}
require_finite(T, "WeightsData", row)?;
}
let mut attrs = surface_attrs(T, control_points, basis)?;
attrs.push(Value::List(
weights
.iter()
.map(|row| Value::List(row.iter().copied().map(Value::Real).collect()))
.collect(),
));
Ok(tx.create(Entity::new(T, attrs)))
}
fn surface_attrs(
type_name: &'static str,
control_points: &[&[EntityId]],
basis: SurfaceBasis<'_>,
) -> Result<Vec<Value>, GeometryError> {
let SurfaceBasis {
degree: (u_degree, v_degree),
u,
v,
form,
knot_spec,
} = basis;
let rows = control_points.len();
if rows < 2 {
return Err(invalid(
type_name,
"ControlPointsList",
format!("expected at least 2 rows, got {rows}"),
));
}
let columns = control_points[0].len();
if columns < 2 {
return Err(invalid(
type_name,
"ControlPointsList",
format!("expected at least 2 columns, got {columns}"),
));
}
if let Some(bad) = control_points.iter().position(|row| row.len() != columns) {
return Err(invalid(
type_name,
"ControlPointsList",
format!(
"row {bad} has {} control points, row 0 has {columns}; the grid must be rectangular",
control_points[bad].len()
),
));
}
check_knots(type_name, "U", u_degree, rows, u)?;
check_knots(type_name, "V", v_degree, columns, v)?;
let mut attrs = vec![Value::Null; 12];
attrs[bspline_slot::U_DEGREE] = Value::Integer(u_degree);
attrs[bspline_slot::V_DEGREE] = Value::Integer(v_degree);
attrs[bspline_slot::CONTROL_POINTS] =
Value::List(control_points.iter().map(|row| refs(row)).collect());
attrs[bspline_slot::SURFACE_FORM] = Value::Enum(form.into());
attrs[bspline_slot::U_CLOSED] = Value::LogicalUnknown;
attrs[bspline_slot::V_CLOSED] = Value::LogicalUnknown;
attrs[bspline_slot::SELF_INTERSECT] = Value::LogicalUnknown;
attrs[bspline_slot::U_MULTIPLICITIES] = integers(u.multiplicities);
attrs[bspline_slot::V_MULTIPLICITIES] = integers(v.multiplicities);
attrs[bspline_slot::U_KNOTS] = reals(u.knots);
attrs[bspline_slot::V_KNOTS] = reals(v.knots);
attrs[bspline_slot::KNOT_SPEC] = Value::Enum(knot_spec.into());
Ok(attrs)
}
fn check_knots(
type_name: &'static str,
axis: &'static str,
degree: i64,
extent: usize,
knots: SurfaceKnots<'_>,
) -> Result<(), GeometryError> {
if degree < 1 {
return Err(invalid(
type_name,
"Degree",
format!("{axis}Degree must be at least 1, got {degree}"),
));
}
if knots.multiplicities.len() != knots.knots.len() {
return Err(invalid(
type_name,
"Multiplicities",
format!(
"{axis}: {} multiplicities for {} knots",
knots.multiplicities.len(),
knots.knots.len()
),
));
}
if knots.knots.len() < 2 {
return Err(invalid(
type_name,
"Knots",
format!("{axis}: expected at least 2 distinct knots"),
));
}
require_finite(type_name, "Knots", knots.knots)?;
if knots.multiplicities.iter().any(|m| *m < 1) {
return Err(invalid(
type_name,
"Multiplicities",
format!("{axis}: a multiplicity is not positive"),
));
}
if knots.knots.windows(2).any(|w| w[1] <= w[0]) {
return Err(invalid(
type_name,
"Knots",
format!("{axis}: knots must strictly increase"),
));
}
let total: i64 = knots.multiplicities.iter().sum();
let expected = degree + extent as i64 + 1;
if total != expected {
return Err(invalid(
type_name,
"Multiplicities",
format!(
"{axis}: multiplicities sum to {total}, but degree {degree} over {extent} control points requires {expected}"
),
));
}
Ok(())
}
fn integers(values: &[i64]) -> Value {
Value::List(values.iter().copied().map(Value::Integer).collect())
}
fn reals(values: &[f64]) -> Value {
Value::List(values.iter().copied().map(Value::Real).collect())
}