1use ifc_model::{Entity, EntityId, Transaction, Value};
23
24use crate::curve::composite::{curve_slot, segment_slot};
25use crate::curve::TransitionCode;
26use crate::error::GeometryError;
27
28use super::{invalid, refs, require_finite};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum SurfaceCurveRepresentation {
33 Curve3D,
35 PCurveS1,
37 PCurveS2,
39}
40
41impl SurfaceCurveRepresentation {
42 fn token(self) -> &'static str {
44 match self {
45 Self::Curve3D => "CURVE3D",
46 Self::PCurveS1 => "PCURVE_S1",
47 Self::PCurveS2 => "PCURVE_S2",
48 }
49 }
50}
51
52pub fn pcurve(
54 tx: &mut Transaction,
55 basis_surface: EntityId,
56 reference_curve: EntityId,
57) -> EntityId {
58 let attrs = vec![Value::Ref(basis_surface), Value::Ref(reference_curve)];
59 tx.create(Entity::new("IFCPCURVE", attrs))
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum SurfaceCurveKind {
65 Plain,
67 Intersection,
69 Seam,
71}
72
73impl SurfaceCurveKind {
74 fn type_name(self) -> &'static str {
76 match self {
77 Self::Plain => "IFCSURFACECURVE",
78 Self::Intersection => "IFCINTERSECTIONCURVE",
79 Self::Seam => "IFCSEAMCURVE",
80 }
81 }
82
83 fn needs_two(self) -> bool {
85 matches!(self, Self::Intersection | Self::Seam)
86 }
87}
88
89pub fn surface_curve(
97 tx: &mut Transaction,
98 kind: SurfaceCurveKind,
99 curve_3d: EntityId,
100 associated_geometry: &[EntityId],
101 master: SurfaceCurveRepresentation,
102) -> Result<EntityId, GeometryError> {
103 let type_name = kind.type_name();
104 if associated_geometry.is_empty() || associated_geometry.len() > 2 {
105 return Err(invalid(
106 type_name,
107 "AssociatedGeometry",
108 format!("expected 1 or 2 pcurves, got {}", associated_geometry.len()),
109 ));
110 }
111 if kind.needs_two() && associated_geometry.len() != 2 {
112 return Err(invalid(
113 type_name,
114 "AssociatedGeometry",
115 "TwoPCurves: this form needs a pcurve on each surface",
116 ));
117 }
118 let attrs = vec![
119 Value::Ref(curve_3d),
120 refs(associated_geometry),
121 Value::Enum(master.token().into()),
122 ];
123 Ok(tx.create(Entity::new(type_name, attrs)))
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum OnSurfaceKind {
129 Composite,
131 Boundary,
133 OuterBoundary,
135}
136
137impl OnSurfaceKind {
138 fn type_name(self) -> &'static str {
140 match self {
141 Self::Composite => "IFCCOMPOSITECURVEONSURFACE",
142 Self::Boundary => "IFCBOUNDARYCURVE",
143 Self::OuterBoundary => "IFCOUTERBOUNDARYCURVE",
144 }
145 }
146
147 fn must_close(self) -> bool {
149 matches!(self, Self::Boundary | Self::OuterBoundary)
150 }
151}
152
153pub fn composite_curve_on_surface(
164 tx: &mut Transaction,
165 kind: OnSurfaceKind,
166 segments: &[EntityId],
167) -> Result<EntityId, GeometryError> {
168 let type_name = kind.type_name();
169 if segments.is_empty() {
170 return Err(invalid(
171 type_name,
172 "Segments",
173 "expected at least one segment",
174 ));
175 }
176 let mut attrs = vec![Value::Null; 2];
177 attrs[curve_slot::SEGMENTS] = refs(segments);
178 attrs[curve_slot::SELF_INTERSECT] = if kind.must_close() {
179 Value::Bool(true)
181 } else {
182 Value::LogicalUnknown
183 };
184 Ok(tx.create(Entity::new(type_name, attrs)))
185}
186
187pub fn reparametrised_composite_curve_segment(
197 tx: &mut Transaction,
198 transition: TransitionCode,
199 same_sense: bool,
200 parent_curve: EntityId,
201 param_length: f64,
202) -> Result<EntityId, GeometryError> {
203 const T: &str = "IFCREPARAMETRISEDCOMPOSITECURVESEGMENT";
204 require_finite(T, "ParamLength", &[param_length])?;
205 if param_length <= 0.0 {
206 return Err(invalid(
207 T,
208 "ParamLength",
209 format!("expected a positive parameter length, got {param_length}"),
210 ));
211 }
212 let mut attrs = vec![Value::Null; 4];
213 attrs[segment_slot::TRANSITION] = Value::Enum(transition.token().into());
214 attrs[segment_slot::SAME_SENSE] = Value::Bool(same_sense);
215 attrs[segment_slot::PARENT_CURVE] = Value::Ref(parent_curve);
216 attrs[segment_slot::PARAM_LENGTH] = Value::Typed {
217 type_name: "IFCPARAMETERVALUE".into(),
218 value: Box::new(Value::Real(param_length)),
219 };
220 Ok(tx.create(Entity::new(T, attrs)))
221}
222
223#[derive(Debug, Clone, Copy)]
230pub enum CurveMeasure {
231 Length(f64),
233 Parameter(f64),
235}
236
237impl CurveMeasure {
238 fn to_value(
240 self,
241 type_name: &'static str,
242 attribute: &'static str,
243 ) -> Result<Value, GeometryError> {
244 let (measure, value) = match self {
245 Self::Length(v) => ("IFCNONNEGATIVELENGTHMEASURE", v),
246 Self::Parameter(v) => ("IFCPARAMETERVALUE", v),
247 };
248 require_finite(type_name, attribute, &[value])?;
249 if matches!(self, Self::Length(_)) && value < 0.0 {
250 return Err(invalid(
251 type_name,
252 attribute,
253 format!("expected a non-negative length, got {value}"),
254 ));
255 }
256 Ok(Value::Typed {
257 type_name: measure.into(),
258 value: Box::new(Value::Real(value)),
259 })
260 }
261}
262
263pub fn curve_segment(
273 tx: &mut Transaction,
274 transition: TransitionCode,
275 placement: EntityId,
276 segment_start: CurveMeasure,
277 segment_length: CurveMeasure,
278 parent_curve: EntityId,
279) -> Result<EntityId, GeometryError> {
280 const T: &str = "IFCCURVESEGMENT";
281 let attrs = vec![
282 Value::Enum(transition.token().into()),
283 Value::Ref(placement),
284 segment_start.to_value(T, "SegmentStart")?,
285 segment_length.to_value(T, "SegmentLength")?,
286 Value::Ref(parent_curve),
287 ];
288 Ok(tx.create(Entity::new(T, attrs)))
289}
290
291pub fn gradient_curve(
301 tx: &mut Transaction,
302 segments: &[EntityId],
303 base_curve: EntityId,
304 end_point: Option<EntityId>,
305) -> Result<EntityId, GeometryError> {
306 const T: &str = "IFCGRADIENTCURVE";
307 if segments.is_empty() {
308 return Err(invalid(T, "Segments", "expected at least one segment"));
309 }
310 let attrs = vec![
311 refs(segments),
312 Value::LogicalUnknown,
313 Value::Ref(base_curve),
314 end_point.map_or(Value::Null, Value::Ref),
315 ];
316 Ok(tx.create(Entity::new(T, attrs)))
317}