use super::arena::Arena;
use super::assembly::AssemblyTree;
use super::geometry::{
Axis1Placement, Axis2Placement2d, Axis2Placement3d, Curve, Curve2d, Direction2, Direction3,
Point2, Point3, Surface,
};
use super::id::Placement3dId;
use super::topology::{Edge, Face, Shell, Solid, Vertex, Wire};
use crate::parser::schema::StepSchema;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NonEmptyStringList(Vec<String>);
impl NonEmptyStringList {
#[must_use]
pub fn single(s: String) -> Self {
Self(vec![s])
}
#[must_use]
pub fn try_from_vec(v: Vec<String>) -> Option<Self> {
if v.is_empty() { None } else { Some(Self(v)) }
}
#[must_use]
pub fn as_slice(&self) -> &[String] {
&self.0
}
pub fn iter(&self) -> std::slice::Iter<'_, String> {
self.0.iter()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
false
}
pub fn push(&mut self, s: String) {
self.0.push(s);
}
}
impl Default for NonEmptyStringList {
fn default() -> Self {
Self::single(String::new())
}
}
impl<'a> IntoIterator for &'a NonEmptyStringList {
type Item = &'a String;
type IntoIter = std::slice::Iter<'a, String>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImplementationLevel(String);
impl ImplementationLevel {
#[must_use]
pub fn v2_1() -> Self {
Self("2;1".into())
}
#[must_use]
pub fn try_from_string(s: String) -> Option<Self> {
if s.is_empty() { None } else { Some(Self(s)) }
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for ImplementationLevel {
fn default() -> Self {
Self::v2_1()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct FileHeader {
pub description: NonEmptyStringList,
pub implementation_level: ImplementationLevel,
pub name: String,
pub time_stamp: String,
pub author: NonEmptyStringList,
pub organization: NonEmptyStringList,
pub preprocessor_version: String,
pub originating_system: String,
pub authorization: String,
}
#[derive(Debug, Clone, Default)]
pub struct StepModel {
pub geometry: GeometryPool,
pub topology: TopologyPool,
pub units: Option<UnitContext>,
pub assembly: Option<AssemblyTree>,
pub schema: StepSchema,
pub header: Option<FileHeader>,
}
#[derive(Debug, Clone, Default)]
pub struct TopologyPool {
pub solids: Arena<Solid>,
pub shells: Arena<Shell>,
pub faces: Arena<Face>,
pub wires: Arena<Wire>,
pub edges: Arena<Edge>,
pub vertices: Arena<Vertex>,
}
#[derive(Debug, Clone, Default)]
pub struct GeometryPool {
pub surfaces: Arena<Surface>,
pub curves: Arena<Curve>,
pub points: Arena<Point3>,
pub directions: Arena<Direction3>,
pub placements: Arena<Axis2Placement3d>,
pub placements_1d: Arena<Axis1Placement>,
pub points_2d: Arena<Point2>,
pub directions_2d: Arena<Direction2>,
pub curves_2d: Arena<Curve2d>,
pub placements_2d: Arena<Axis2Placement2d>,
identity_placement_cache: Option<Placement3dId>,
}
impl GeometryPool {
pub fn identity_placement(&mut self) -> Placement3dId {
if let Some(id) = self.identity_placement_cache {
return id;
}
let origin = self.points.push(Point3 {
x: 0.0,
y: 0.0,
z: 0.0,
});
let id = self.placements.push(Axis2Placement3d {
location: origin,
axis: None,
ref_direction: None,
});
self.identity_placement_cache = Some(id);
id
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UnitContext {
pub length: LengthUnit,
pub plane_angle: AngleUnit,
pub solid_angle: SolidAngleUnit,
pub length_uncertainty: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LengthUnit {
Millimetre,
Metre,
Centimetre,
Inch,
Foot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AngleUnit {
Radian,
Degree,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolidAngleUnit {
Steradian,
}
#[cfg(test)]
mod tests {
use super::{FileHeader, ImplementationLevel, NonEmptyStringList};
#[test]
fn non_empty_string_list_try_from_empty_vec_is_none() {
assert!(NonEmptyStringList::try_from_vec(vec![]).is_none());
}
#[test]
fn non_empty_string_list_try_from_populated_vec_is_some() {
let v = vec!["a".into(), "b".into()];
let nel = NonEmptyStringList::try_from_vec(v).expect("non-empty");
assert_eq!(nel.len(), 2);
assert_eq!(nel.as_slice()[0], "a");
}
#[test]
fn non_empty_string_list_default_is_single_empty_string() {
let nel = NonEmptyStringList::default();
assert_eq!(nel.len(), 1);
assert_eq!(nel.as_slice()[0], "");
}
#[test]
fn implementation_level_try_from_empty_is_none() {
assert!(ImplementationLevel::try_from_string(String::new()).is_none());
}
#[test]
fn implementation_level_try_from_non_empty_is_some() {
let il = ImplementationLevel::try_from_string("2;1".into()).expect("non-empty");
assert_eq!(il.as_str(), "2;1");
}
#[test]
fn implementation_level_default_is_v2_1() {
assert_eq!(ImplementationLevel::default().as_str(), "2;1");
}
#[test]
fn file_header_default_passes_spec_constraints() {
let h = FileHeader::default();
assert_eq!(h.description.len(), 1);
assert_eq!(h.author.len(), 1);
assert_eq!(h.organization.len(), 1);
assert!(!h.implementation_level.as_str().is_empty());
}
}