///|
/// ApproximationKind classifies how a modelled feature departs from the ideal
/// mathematical surface.
///
/// The list is deliberately short and explicit: a feature is either modelled
/// exactly (and listed in `FeatureRecord::modeled`), approximated by one of
/// these documented schemes, or omitted with a reason. Labelling a sharp edge
/// as a chamfer is not an option.
pub(all) enum ApproximationKind {
/// A continuously curved feature sampled as a short polyline/arc chain.
PolylineArc
/// A curved surface of revolution represented by a polygon ring stack.
PolygonRevolve
/// A swept/lofted feature sampled along its path.
SweptSpline
/// A feature whose size is an engineering default rather than a standard
/// dimension.
EngineeringDefault
}
///|
/// ApproximationKind::to_string returns a stable machine-readable token.
pub fn ApproximationKind::to_string(self : ApproximationKind) -> String {
match self {
PolylineArc => "PolylineArc"
PolygonRevolve => "PolygonRevolve"
SweptSpline => "SweptSpline"
EngineeringDefault => "EngineeringDefault"
}
}
///|
/// Approximation records one approximated feature with its stated deviation
/// and the control that produced it, so a downstream consumer can decide
/// whether the model is good enough for its purpose.
pub(all) struct Approximation {
/// Feature token, e.g. "head-socket-band".
feature : String
kind : ApproximationKind
/// Maximum geometric deviation in millimetres (chord/sag error).
deviation_mm : Double
/// Generator control, e.g. "circle_segments=64".
control : String
}
///|
/// Approximation::new builds an approximation record.
pub fn Approximation::new(
feature~ : String,
kind~ : ApproximationKind,
deviation_mm~ : Double,
control~ : String,
) -> Approximation {
{ feature, kind, deviation_mm, control, }
}
///|
/// Approximation::documentation returns a one-line human-readable record.
pub fn Approximation::documentation(self : Approximation) -> String {
"\{self.feature} (\{self.kind.to_string()}, deviation \{self.deviation_mm} mm, control \{self.control})"
}
///|
/// Omission records a feature that is intentionally absent, with a reason.
///
/// A simplified part is not a liar only when it says what it left out.
pub(all) struct Omission {
/// Feature token, e.g. "head-chamfer".
feature : String
/// Why the feature is absent, e.g. "omitted at Simplified detail level".
reason : String
}
///|
/// Omission::new builds an omission record.
pub fn Omission::new(feature~ : String, reason~ : String) -> Omission {
{ feature, reason, }
}
///|
/// Omission::documentation returns a one-line human-readable record.
pub fn Omission::documentation(self : Omission) -> String {
"\{self.feature} (\{self.reason})"
}
///|
/// FeatureRecord is the machine-readable statement of which features a
/// component models, approximates, or omits.
///
/// It is carried on every `Component`, copied into the STEP header
/// documentation, and asserted by tests. `tolerance_mm` is the component's
/// stated geometric tolerance for the modelled representation as a whole.
pub(all) struct FeatureRecord {
/// Exactly modelled features (analytic surfaces or exact planar/ruled
/// geometry).
modeled : Array[String]
/// Approximated features with a stated deviation and control.
approximated : Array[Approximation]
/// Omitted features with a documented reason.
omitted : Array[Omission]
/// Overall stated representation tolerance in millimetres.
tolerance_mm : Double
}
///|
/// FeatureRecord::empty returns a record that claims no modelled features and
/// no approximations. Used as the default before a builder fills it in.
pub fn FeatureRecord::empty() -> FeatureRecord {
{ modeled: [], approximated: [], omitted: [], tolerance_mm: 0.01, }
}
///|
/// FeatureRecord::new builds a feature record.
pub fn FeatureRecord::new(
modeled~ : Array[String] = [],
approximated~ : Array[Approximation] = [],
omitted~ : Array[Omission] = [],
tolerance_mm~ : Double = 0.01,
) -> FeatureRecord {
{ modeled, approximated, omitted, tolerance_mm, }
}
///|
/// FeatureRecord::with_modeled returns a copy with the modelled feature list.
pub fn FeatureRecord::with_modeled(
self : FeatureRecord,
modeled : Array[String],
) -> FeatureRecord {
{ ..self, modeled, }
}
///|
/// FeatureRecord::with_approximated returns a copy with the approximations.
pub fn FeatureRecord::with_approximated(
self : FeatureRecord,
approximated : Array[Approximation],
) -> FeatureRecord {
{ ..self, approximated, }
}
///|
/// FeatureRecord::with_omitted returns a copy with the omissions.
pub fn FeatureRecord::with_omitted(
self : FeatureRecord,
omitted : Array[Omission],
) -> FeatureRecord {
{ ..self, omitted, }
}
///|
/// FeatureRecord::with_tolerance returns a copy with a new overall tolerance.
pub fn FeatureRecord::with_tolerance(
self : FeatureRecord,
tolerance_mm : Double,
) -> FeatureRecord {
{ ..self, tolerance_mm, }
}
///|
/// FeatureRecord::add_modeled returns a copy with one more modelled feature.
pub fn FeatureRecord::add_modeled(
self : FeatureRecord,
feature : String,
) -> FeatureRecord {
let modeled = self.modeled.copy()
modeled.push(feature)
{ ..self, modeled, }
}
///|
/// FeatureRecord::add_approximated returns a copy with one more approximation.
pub fn FeatureRecord::add_approximated(
self : FeatureRecord,
approximation : Approximation,
) -> FeatureRecord {
let approximated = self.approximated.copy()
approximated.push(approximation)
{ ..self, approximated, }
}
///|
/// FeatureRecord::add_omitted returns a copy with one more omission.
pub fn FeatureRecord::add_omitted(
self : FeatureRecord,
omission : Omission,
) -> FeatureRecord {
let omitted = self.omitted.copy()
omitted.push(omission)
{ ..self, omitted, }
}
///|
/// FeatureRecord::has_modeled reports whether `feature` is in the modelled
/// list.
pub fn FeatureRecord::has_modeled(
self : FeatureRecord,
feature : String,
) -> Bool {
self.modeled.any(fn(f) { f == feature })
}
///|
/// FeatureRecord::has_approximated reports whether `feature` is approximated.
pub fn FeatureRecord::has_approximated(
self : FeatureRecord,
feature : String,
) -> Bool {
self.approximated.any(fn(a) { a.feature == feature })
}
///|
/// FeatureRecord::has_omitted reports whether `feature` is in the omitted list.
pub fn FeatureRecord::has_omitted(
self : FeatureRecord,
feature : String,
) -> Bool {
self.omitted.any(fn(o) { o.feature == feature })
}
///|
/// FeatureRecord::omission_reason returns the documented reason a feature is
/// omitted, or `None` when it is not omitted.
pub fn FeatureRecord::omission_reason(
self : FeatureRecord,
feature : String,
) -> String? {
for o in self.omitted {
if o.feature == feature {
return Some(o.reason)
}
}
None
}
///|
/// FeatureRecord::documentation returns the multi-line STEP header block.
pub fn FeatureRecord::documentation(self : FeatureRecord) -> String {
let lines : Array[String] = []
if !self.modeled.is_empty() {
lines.push("Features modeled: " + self.modeled.join(", "))
}
if !self.approximated.is_empty() {
lines.push(
"Features approximated: " +
self.approximated.map(fn(a) { a.documentation() }).join("; "),
)
}
if !self.omitted.is_empty() {
lines.push(
"Features omitted: " +
self.omitted.map(fn(o) { o.documentation() }).join("; "),
)
}
lines.push("Feature tolerance: " + self.tolerance_mm.to_string() + " mm")
lines.join("\n")
}
///|
/// chord_deviation returns the maximum sag (chord) error of a circle of
/// `radius` sampled into `segments` chords.
///
/// It is `r * (1 - cos(pi / segments))`, the exact deviation used to justify a
/// polyline approximation.
pub fn chord_deviation(radius : Double, segments : Int32) -> Double {
let n = if segments < 3 { 3 } else { segments }
radius * (1.0 - @math.cos(@math.PI / Double::from_int(n)))
}