use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::curve::bspline::slot as bspline_slot;
use crate::curve::composite::{curve_slot, segment_slot};
use crate::curve::conic::{circle_slot, ellipse_slot};
use crate::curve::line::slot as line_slot;
use crate::curve::polyline::indexed_slot;
use crate::curve::trimmed::{slot as trimmed_slot, Trim};
use crate::curve::{TransitionCode, TrimmingPreference};
use crate::error::GeometryError;
use super::std_profile::positive;
use super::{invalid, reals, refs, require_finite};
pub fn vector(
tx: &mut Transaction,
orientation: EntityId,
magnitude: f64,
) -> Result<EntityId, GeometryError> {
positive("IFCVECTOR", "Magnitude", magnitude)?;
let attrs = vec![Value::Ref(orientation), Value::Real(magnitude)];
Ok(tx.create(Entity::new("IFCVECTOR", attrs)))
}
pub fn line(tx: &mut Transaction, point: EntityId, dir: EntityId) -> EntityId {
let mut attrs = vec![Value::Null; 2];
attrs[line_slot::PNT] = Value::Ref(point);
attrs[line_slot::DIR] = Value::Ref(dir);
tx.create(Entity::new("IFCLINE", attrs))
}
pub fn circle(
tx: &mut Transaction,
position: EntityId,
radius: f64,
) -> Result<EntityId, GeometryError> {
positive("IFCCIRCLE", "Radius", radius)?;
let mut attrs = vec![Value::Null; 2];
attrs[circle_slot::POSITION] = Value::Ref(position);
attrs[circle_slot::RADIUS] = Value::Real(radius);
Ok(tx.create(Entity::new("IFCCIRCLE", attrs)))
}
pub fn ellipse(
tx: &mut Transaction,
position: EntityId,
semi_axis_1: f64,
semi_axis_2: f64,
) -> Result<EntityId, GeometryError> {
positive("IFCELLIPSE", "SemiAxis1", semi_axis_1)?;
positive("IFCELLIPSE", "SemiAxis2", semi_axis_2)?;
let mut attrs = vec![Value::Null; 3];
attrs[ellipse_slot::POSITION] = Value::Ref(position);
attrs[ellipse_slot::SEMI_AXIS_1] = Value::Real(semi_axis_1);
attrs[ellipse_slot::SEMI_AXIS_2] = Value::Real(semi_axis_2);
Ok(tx.create(Entity::new("IFCELLIPSE", attrs)))
}
fn trim_members(trim: Trim, which: &'static str) -> Result<Value, GeometryError> {
let mut set = Vec::with_capacity(2);
if let Some(point) = trim.cartesian {
set.push(Value::Ref(point));
}
if let Some(parameter) = trim.parameter {
require_finite("IFCTRIMMEDCURVE", which, &[parameter])?;
set.push(Value::Typed {
type_name: "IFCPARAMETERVALUE".into(),
value: Box::new(Value::Real(parameter)),
});
}
if set.is_empty() {
return Err(invalid(
"IFCTRIMMEDCURVE",
which,
"a trim needs at least a Cartesian point or a parameter",
));
}
Ok(Value::List(set))
}
pub fn trimmed_curve(
tx: &mut Transaction,
basis: EntityId,
trim_1: Trim,
trim_2: Trim,
sense_agreement: bool,
master: TrimmingPreference,
) -> Result<EntityId, GeometryError> {
let mut attrs = vec![Value::Null; 5];
attrs[trimmed_slot::BASIS_CURVE] = Value::Ref(basis);
attrs[trimmed_slot::TRIM_1] = trim_members(trim_1, "Trim1")?;
attrs[trimmed_slot::TRIM_2] = trim_members(trim_2, "Trim2")?;
attrs[trimmed_slot::SENSE_AGREEMENT] = Value::Bool(sense_agreement);
attrs[trimmed_slot::MASTER_REPRESENTATION] = Value::Enum(master.token().into());
Ok(tx.create(Entity::new("IFCTRIMMEDCURVE", attrs)))
}
pub fn composite_curve_segment(
tx: &mut Transaction,
transition: TransitionCode,
same_sense: bool,
parent_curve: EntityId,
) -> EntityId {
let mut attrs = vec![Value::Null; 3];
attrs[segment_slot::TRANSITION] = Value::Enum(transition.token().into());
attrs[segment_slot::SAME_SENSE] = Value::Bool(same_sense);
attrs[segment_slot::PARENT_CURVE] = Value::Ref(parent_curve);
tx.create(Entity::new("IFCCOMPOSITECURVESEGMENT", attrs))
}
pub fn composite_curve(
tx: &mut Transaction,
segments: &[EntityId],
self_intersect: Option<bool>,
) -> Result<EntityId, GeometryError> {
if segments.is_empty() {
return Err(invalid(
"IFCCOMPOSITECURVE",
"Segments",
"expected at least one segment",
));
}
let mut attrs = vec![Value::Null; 2];
attrs[curve_slot::SEGMENTS] = refs(segments);
attrs[curve_slot::SELF_INTERSECT] = match self_intersect {
Some(value) => Value::Bool(value),
None => Value::LogicalUnknown,
};
Ok(tx.create(Entity::new("IFCCOMPOSITECURVE", attrs)))
}
pub fn offset_curve_2d(
tx: &mut Transaction,
basis: EntityId,
distance: f64,
self_intersect: Option<bool>,
) -> Result<EntityId, GeometryError> {
require_finite("IFCOFFSETCURVE2D", "Distance", &[distance])?;
let attrs = vec![
Value::Ref(basis),
Value::Real(distance),
logical(self_intersect),
];
Ok(tx.create(Entity::new("IFCOFFSETCURVE2D", attrs)))
}
pub fn offset_curve_3d(
tx: &mut Transaction,
basis: EntityId,
distance: f64,
self_intersect: Option<bool>,
ref_direction: EntityId,
) -> Result<EntityId, GeometryError> {
require_finite("IFCOFFSETCURVE3D", "Distance", &[distance])?;
let attrs = vec![
Value::Ref(basis),
Value::Real(distance),
logical(self_intersect),
Value::Ref(ref_direction),
];
Ok(tx.create(Entity::new("IFCOFFSETCURVE3D", attrs)))
}
fn logical(value: Option<bool>) -> Value {
match value {
Some(value) => Value::Bool(value),
None => Value::LogicalUnknown,
}
}
#[derive(Debug, Clone, Copy)]
pub struct KnotVector<'a> {
pub multiplicities: &'a [i64],
pub knots: &'a [f64],
pub spec: &'a str,
}
pub fn bspline_curve_with_knots(
tx: &mut Transaction,
degree: i64,
control_points: &[EntityId],
curve_form: &str,
knots: KnotVector<'_>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCBSPLINECURVEWITHKNOTS";
let attrs = bspline_attrs(T, degree, control_points, curve_form, knots)?;
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn rational_bspline_curve_with_knots(
tx: &mut Transaction,
degree: i64,
control_points: &[EntityId],
curve_form: &str,
knots: KnotVector<'_>,
weights: &[f64],
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCRATIONALBSPLINECURVEWITHKNOTS";
if weights.len() != control_points.len() {
return Err(invalid(
T,
"WeightsData",
format!(
"{} weights for {} control points",
weights.len(),
control_points.len()
),
));
}
require_finite(T, "WeightsData", weights)?;
let mut attrs = bspline_attrs(T, degree, control_points, curve_form, knots)?;
attrs.push(Value::List(
weights.iter().copied().map(Value::Real).collect(),
));
Ok(tx.create(Entity::new(T, attrs)))
}
fn bspline_attrs(
type_name: &'static str,
degree: i64,
control_points: &[EntityId],
curve_form: &str,
knots: KnotVector<'_>,
) -> Result<Vec<Value>, GeometryError> {
if degree < 1 {
return Err(invalid(
type_name,
"Degree",
format!("expected a degree of at least 1, got {degree}"),
));
}
if control_points.len() < 2 {
return Err(invalid(
type_name,
"ControlPointsList",
format!(
"expected at least 2 control points, got {}",
control_points.len()
),
));
}
if knots.multiplicities.len() != knots.knots.len() {
return Err(invalid(
type_name,
"KnotMultiplicities",
format!(
"{} multiplicities for {} knots",
knots.multiplicities.len(),
knots.knots.len()
),
));
}
if knots.knots.len() < 2 {
return Err(invalid(
type_name,
"Knots",
"expected at least 2 distinct knots",
));
}
require_finite(type_name, "Knots", knots.knots)?;
if let Some(bad) = knots.multiplicities.iter().position(|m| *m < 1) {
return Err(invalid(
type_name,
"KnotMultiplicities",
format!("multiplicity at index {bad} is not positive"),
));
}
if let Some(bad) = knots.knots.windows(2).position(|w| w[1] <= w[0]) {
return Err(invalid(
type_name,
"Knots",
format!("knots must strictly increase; index {bad} does not"),
));
}
let total: i64 = knots.multiplicities.iter().sum();
let expected = degree + control_points.len() as i64 + 1;
if total != expected {
return Err(invalid(
type_name,
"KnotMultiplicities",
format!(
"multiplicities sum to {total}, but degree {degree} with {} control points requires {expected}",
control_points.len()
),
));
}
let mut attrs = vec![Value::Null; 8];
attrs[bspline_slot::DEGREE] = Value::Integer(degree);
attrs[bspline_slot::CONTROL_POINTS] = refs(control_points);
attrs[bspline_slot::CURVE_FORM] = Value::Enum(curve_form.into());
attrs[bspline_slot::CLOSED_CURVE] = Value::LogicalUnknown;
attrs[bspline_slot::SELF_INTERSECT] = Value::LogicalUnknown;
attrs[bspline_slot::KNOT_MULTIPLICITIES] = Value::List(
knots
.multiplicities
.iter()
.copied()
.map(Value::Integer)
.collect(),
);
attrs[bspline_slot::KNOTS] =
Value::List(knots.knots.iter().copied().map(Value::Real).collect());
attrs[bspline_slot::KNOT_SPEC] = Value::Enum(knots.spec.into());
Ok(attrs)
}
pub fn indexed_poly_curve(
tx: &mut Transaction,
points: EntityId,
segments: Option<&[PolyCurveSegment<'_>]>,
point_count: usize,
self_intersect: Option<bool>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCINDEXEDPOLYCURVE";
let mut attrs = vec![Value::Null; 3];
attrs[indexed_slot::POINTS] = Value::Ref(points);
if let Some(segments) = segments {
let mut list = Vec::with_capacity(segments.len());
for segment in segments {
list.push(segment.to_value(T, point_count)?);
}
attrs[indexed_slot::SEGMENTS] = Value::List(list);
}
attrs[indexed_slot::SELF_INTERSECT] = logical(self_intersect);
Ok(tx.create(Entity::new(T, attrs)))
}
#[derive(Debug, Clone, Copy)]
pub enum PolyCurveSegment<'a> {
Line(&'a [usize]),
Arc([usize; 3]),
}
impl PolyCurveSegment<'_> {
fn to_value(self, type_name: &'static str, point_count: usize) -> Result<Value, GeometryError> {
let (label, indices): (&str, &[usize]) = match self {
Self::Line(indices) => ("IFCLINEINDEX", indices),
Self::Arc(ref indices) => ("IFCARCINDEX", indices),
};
if let Self::Line(indices) = self {
if indices.len() < 2 {
return Err(invalid(
type_name,
"Segments",
format!(
"a line index needs at least 2 points, got {}",
indices.len()
),
));
}
}
for index in indices {
if *index >= point_count {
return Err(invalid(
type_name,
"Segments",
format!("index {index} is past the {point_count} point list"),
));
}
}
let encoded = indices
.iter()
.map(|index| Value::Integer(*index as i64 + 1))
.collect();
Ok(Value::Typed {
type_name: label.into(),
value: Box::new(Value::List(encoded)),
})
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PolynomialCoefficients<'a> {
pub x: Option<&'a [f64]>,
pub y: Option<&'a [f64]>,
pub z: Option<&'a [f64]>,
}
pub fn polynomial_curve(
tx: &mut Transaction,
position: EntityId,
coefficients: PolynomialCoefficients<'_>,
position_is_3d: bool,
) -> Result<EntityId, GeometryError> {
const ENTITY: &str = "IFCPOLYNOMIALCURVE";
let PolynomialCoefficients { x, y, z } = coefficients;
if z.is_some() && !position_is_3d {
return Err(invalid(
ENTITY,
"CoefficientsZ",
"a 2D position cannot carry Z coefficients",
));
}
let given = [x, y, z].iter().filter(|c| c.is_some()).count();
if given < 2 {
return Err(invalid(
ENTITY,
"Coefficients",
"expected at least two of X, Y, Z, per ValidCoefficients",
));
}
for (values, attribute) in [
(x, "CoefficientsX"),
(y, "CoefficientsY"),
(z, "CoefficientsZ"),
] {
let Some(values) = values else { continue };
if values.len() < 2 {
return Err(invalid(ENTITY, attribute, "expected LIST [2:?]"));
}
require_finite(ENTITY, attribute, values)?;
}
let attrs = vec![
Value::Ref(position),
x.map_or(Value::Null, reals),
y.map_or(Value::Null, reals),
z.map_or(Value::Null, reals),
];
Ok(tx.create(Entity::new(ENTITY, attrs)))
}
pub fn offset_curve_by_distances(
tx: &mut Transaction,
basis: EntityId,
offset_values: &[EntityId],
tag: Option<&str>,
) -> Result<EntityId, GeometryError> {
const ENTITY: &str = "IFCOFFSETCURVEBYDISTANCES";
if offset_values.is_empty() {
return Err(invalid(
ENTITY,
"OffsetValues",
"expected at least one offset, per LIST [1:?]",
));
}
let attrs = vec![
Value::Ref(basis),
refs(offset_values),
tag.map_or(Value::Null, |t| Value::Text(t.into())),
];
Ok(tx.create(Entity::new(ENTITY, attrs)))
}
pub fn segmented_reference_curve(
tx: &mut Transaction,
segments: &[EntityId],
self_intersect: Option<bool>,
base_curve: EntityId,
end_point: Option<EntityId>,
) -> Result<EntityId, GeometryError> {
const ENTITY: &str = "IFCSEGMENTEDREFERENCECURVE";
if segments.is_empty() {
return Err(invalid(
ENTITY,
"Segments",
"expected at least one segment, per LIST [1:?]",
));
}
let attrs = vec![
refs(segments),
logical(self_intersect),
Value::Ref(base_curve),
end_point.map_or(Value::Null, Value::Ref),
];
Ok(tx.create(Entity::new(ENTITY, attrs)))
}