use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::curve::composite::{curve_slot, segment_slot};
use crate::curve::TransitionCode;
use crate::error::GeometryError;
use super::{invalid, refs, require_finite};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SurfaceCurveRepresentation {
Curve3D,
PCurveS1,
PCurveS2,
}
impl SurfaceCurveRepresentation {
fn token(self) -> &'static str {
match self {
Self::Curve3D => "CURVE3D",
Self::PCurveS1 => "PCURVE_S1",
Self::PCurveS2 => "PCURVE_S2",
}
}
}
pub fn pcurve(
tx: &mut Transaction,
basis_surface: EntityId,
reference_curve: EntityId,
) -> EntityId {
let attrs = vec![Value::Ref(basis_surface), Value::Ref(reference_curve)];
tx.create(Entity::new("IFCPCURVE", attrs))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SurfaceCurveKind {
Plain,
Intersection,
Seam,
}
impl SurfaceCurveKind {
fn type_name(self) -> &'static str {
match self {
Self::Plain => "IFCSURFACECURVE",
Self::Intersection => "IFCINTERSECTIONCURVE",
Self::Seam => "IFCSEAMCURVE",
}
}
fn needs_two(self) -> bool {
matches!(self, Self::Intersection | Self::Seam)
}
}
pub fn surface_curve(
tx: &mut Transaction,
kind: SurfaceCurveKind,
curve_3d: EntityId,
associated_geometry: &[EntityId],
master: SurfaceCurveRepresentation,
) -> Result<EntityId, GeometryError> {
let type_name = kind.type_name();
if associated_geometry.is_empty() || associated_geometry.len() > 2 {
return Err(invalid(
type_name,
"AssociatedGeometry",
format!("expected 1 or 2 pcurves, got {}", associated_geometry.len()),
));
}
if kind.needs_two() && associated_geometry.len() != 2 {
return Err(invalid(
type_name,
"AssociatedGeometry",
"TwoPCurves: this form needs a pcurve on each surface",
));
}
let attrs = vec![
Value::Ref(curve_3d),
refs(associated_geometry),
Value::Enum(master.token().into()),
];
Ok(tx.create(Entity::new(type_name, attrs)))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OnSurfaceKind {
Composite,
Boundary,
OuterBoundary,
}
impl OnSurfaceKind {
fn type_name(self) -> &'static str {
match self {
Self::Composite => "IFCCOMPOSITECURVEONSURFACE",
Self::Boundary => "IFCBOUNDARYCURVE",
Self::OuterBoundary => "IFCOUTERBOUNDARYCURVE",
}
}
fn must_close(self) -> bool {
matches!(self, Self::Boundary | Self::OuterBoundary)
}
}
pub fn composite_curve_on_surface(
tx: &mut Transaction,
kind: OnSurfaceKind,
segments: &[EntityId],
) -> Result<EntityId, GeometryError> {
let type_name = kind.type_name();
if segments.is_empty() {
return Err(invalid(
type_name,
"Segments",
"expected at least one segment",
));
}
let mut attrs = vec![Value::Null; 2];
attrs[curve_slot::SEGMENTS] = refs(segments);
attrs[curve_slot::SELF_INTERSECT] = if kind.must_close() {
Value::Bool(true)
} else {
Value::LogicalUnknown
};
Ok(tx.create(Entity::new(type_name, attrs)))
}
pub fn reparametrised_composite_curve_segment(
tx: &mut Transaction,
transition: TransitionCode,
same_sense: bool,
parent_curve: EntityId,
param_length: f64,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCREPARAMETRISEDCOMPOSITECURVESEGMENT";
require_finite(T, "ParamLength", &[param_length])?;
if param_length <= 0.0 {
return Err(invalid(
T,
"ParamLength",
format!("expected a positive parameter length, got {param_length}"),
));
}
let mut attrs = vec![Value::Null; 4];
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);
attrs[segment_slot::PARAM_LENGTH] = Value::Real(param_length);
Ok(tx.create(Entity::new(T, attrs)))
}
#[derive(Debug, Clone, Copy)]
pub enum CurveMeasure {
Length(f64),
Parameter(f64),
}
impl CurveMeasure {
fn to_value(
self,
type_name: &'static str,
attribute: &'static str,
) -> Result<Value, GeometryError> {
let (measure, value) = match self {
Self::Length(v) => ("IFCNONNEGATIVELENGTHMEASURE", v),
Self::Parameter(v) => ("IFCPARAMETERVALUE", v),
};
require_finite(type_name, attribute, &[value])?;
if matches!(self, Self::Length(_)) && value < 0.0 {
return Err(invalid(
type_name,
attribute,
format!("expected a non-negative length, got {value}"),
));
}
Ok(Value::Typed {
type_name: measure.into(),
value: Box::new(Value::Real(value)),
})
}
}
pub fn curve_segment(
tx: &mut Transaction,
transition: TransitionCode,
placement: EntityId,
segment_start: CurveMeasure,
segment_length: CurveMeasure,
parent_curve: EntityId,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCCURVESEGMENT";
let attrs = vec![
Value::Enum(transition.token().into()),
Value::Ref(placement),
segment_start.to_value(T, "SegmentStart")?,
segment_length.to_value(T, "SegmentLength")?,
Value::Ref(parent_curve),
];
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn gradient_curve(
tx: &mut Transaction,
segments: &[EntityId],
base_curve: EntityId,
end_point: Option<EntityId>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCGRADIENTCURVE";
if segments.is_empty() {
return Err(invalid(T, "Segments", "expected at least one segment"));
}
let attrs = vec![
refs(segments),
Value::LogicalUnknown,
Value::Ref(base_curve),
end_point.map_or(Value::Null, Value::Ref),
];
Ok(tx.create(Entity::new(T, attrs)))
}