use crate::generated::author::{Ap242Author, AuthorError};
use crate::generated::model as m;
use crate::header::FileHeader;
pub use crate::scene::{NurbsCurve, NurbsSurface, Rgb};
#[derive(Debug, Clone, Default)]
pub struct HeaderInput {
pub file_name: Option<String>,
pub description: Option<String>,
pub authors: Vec<String>,
pub organizations: Vec<String>,
pub originating_system: Option<String>,
pub authorisation: Option<String>,
pub timestamp: Option<String>,
}
fn iso_utc_from_unix(secs: u64) -> String {
let days = secs / 86_400;
let rem = secs % 86_400;
let (hour, minute, second) = (rem / 3600, (rem % 3600) / 60, rem % 60);
let z = days + 719_468;
let era = z / 146_097;
let doe = z % 146_097;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let year = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = doy - (153 * mp + 2) / 5 + 1;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if month <= 2 { year + 1 } else { year };
format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z")
}
fn now_iso_utc() -> String {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
iso_utc_from_unix(secs)
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum LengthUnit {
#[default]
Millimetre,
Metre,
}
#[derive(Debug, Clone, Copy)]
pub struct UnitsInput {
pub length: LengthUnit,
pub uncertainty: f64,
}
impl Default for UnitsInput {
fn default() -> Self {
Self {
length: LengthUnit::Millimetre,
uncertainty: 1.0e-7,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Frame {
pub origin: [f64; 3],
pub axis: [f64; 3],
pub ref_dir: [f64; 3],
}
#[derive(Debug, Clone)]
pub enum SurfaceInput {
Plane(Frame),
Cylinder(Frame, f64),
Sphere(Frame, f64),
Torus(Frame, f64, f64),
Cone(Frame, f64, f64),
LinearExtrusion(ProfileInput, [f64; 3]),
Revolution(ProfileInput, [f64; 3], [f64; 3]),
Nurbs(NurbsSurface),
}
#[derive(Debug, Clone)]
pub enum CurveInput {
Line,
Circle(Frame, f64),
Ellipse(Frame, f64, f64),
Polyline(Vec<[f64; 3]>),
Nurbs(NurbsCurve),
}
#[derive(Debug, Clone)]
pub enum ProfileInput {
Line([f64; 3], [f64; 3]),
Circle(Frame, f64),
Ellipse(Frame, f64, f64),
Nurbs(NurbsCurve),
}
#[derive(Debug, Clone)]
pub struct FaceBoundInput {
pub edges: Vec<(m::EdgeCurveId, bool)>,
pub outer: bool,
pub orientation: bool,
}
impl FaceBoundInput {
#[must_use]
pub fn outer(edges: Vec<(m::EdgeCurveId, bool)>) -> Self {
Self {
edges,
outer: true,
orientation: true,
}
}
#[must_use]
pub fn inner(edges: Vec<(m::EdgeCurveId, bool)>) -> Self {
Self {
edges,
outer: false,
orientation: true,
}
}
}
fn compress_knots(expanded: &[f64]) -> (Vec<f64>, Vec<i64>) {
let mut knots: Vec<f64> = Vec::new();
let mut multiplicities: Vec<i64> = Vec::new();
for &k in expanded {
if knots.last() == Some(&k) {
*multiplicities.last_mut().unwrap() += 1;
} else {
knots.push(k);
multiplicities.push(1);
}
}
(knots, multiplicities)
}
#[derive(Debug, Clone, Default)]
pub struct PartOptions {
pub id: Option<String>,
pub description: Option<String>,
pub version: Option<String>,
pub version_description: Option<String>,
pub definition_description: Option<String>,
}
#[derive(Debug, Clone)]
pub struct PersonOrg {
pub person_id: String,
pub first_name: Option<String>,
pub last_name: Option<String>,
pub organization: String,
}
#[derive(Debug, Clone, Copy)]
pub struct DateTimeInput {
pub year: i64,
pub month: i64,
pub day: i64,
pub hour: i64,
pub minute: Option<i64>,
}
#[derive(Debug, Clone, Default)]
pub struct ApprovalInput {
pub status: String,
pub level: String,
pub approvers: Vec<(m::PersonAndOrganizationId, String)>,
pub date: Option<DateTimeInput>,
}
#[derive(Debug, Clone)]
pub struct DocumentInput {
pub id: String,
pub name: String,
pub description: Option<String>,
pub kind: String,
}
#[derive(Debug, Clone, Default)]
pub enum MeshNormalsInput {
#[default]
None,
Uniform([f64; 3]),
PerVertex(Vec<[f64; 3]>),
}
#[derive(Debug, Clone)]
pub struct MeshInput {
pub points: Vec<[f64; 3]>,
pub triangles: Vec<[usize; 3]>,
pub normals: MeshNormalsInput,
}
#[derive(Debug, Clone, Copy)]
pub enum StyleTarget {
Face(m::AdvancedFaceId),
Solid(m::ManifoldSolidBrepId),
VoidSolid(m::BrepWithVoidsId),
}
#[derive(Debug, Clone, Copy)]
pub enum SolidRef {
Solid(m::ManifoldSolidBrepId),
VoidSolid(m::BrepWithVoidsId),
}
impl From<m::ManifoldSolidBrepId> for SolidRef {
fn from(id: m::ManifoldSolidBrepId) -> Self {
SolidRef::Solid(id)
}
}
impl From<m::BrepWithVoidsId> for SolidRef {
fn from(id: m::BrepWithVoidsId) -> Self {
SolidRef::VoidSolid(id)
}
}
#[derive(Debug, Clone, Copy)]
pub enum VoidShellNormals {
AwayFromMaterial,
TowardMaterial,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Vertex(usize);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Part(usize);
struct PendingPart {
product: m::ProductId,
formation: m::ProductDefinitionFormationId,
definition: m::ProductDefinitionId,
shape: m::ProductDefinitionShapeId,
origin: m::Axis2Placement3dId,
items: Vec<m::RepresentationItemRef>,
meshes: Vec<m::TessellatedSolidId>,
}
struct PendingPlacement {
nauo: m::NextAssemblyUsageOccurrenceId,
parent: usize,
child: usize,
to: m::Axis2Placement3dId,
}
pub struct StepBuilder {
author: Ap242Author,
ctx: m::ComplexUnitId,
product_context: m::ProductContextId,
pd_context: m::ProductDefinitionContextId,
parts: Vec<PendingPart>,
placements: Vec<PendingPlacement>,
vertices: Vec<(m::VertexPointId, [f64; 3])>,
styles: Vec<m::StyledItemId>,
hidden: Vec<m::InvisibleItemRef>,
header: HeaderInput,
}
impl StepBuilder {
pub fn new() -> Result<Self, AuthorError> {
Self::new_with(&UnitsInput::default())
}
pub fn new_with(units: &UnitsInput) -> Result<Self, AuthorError> {
let mut a = Ap242Author::new();
let ac = a.add_application_context("managed model based 3d engineering".to_owned())?;
a.add_application_protocol_definition(
"international standard".to_owned(),
"ap242_managed_model_based_3d_engineering_mim_lf".to_owned(),
2011,
m::ApplicationContextRef::ApplicationContext(ac),
)?;
let product_context = a.add_product_context(
String::new(),
m::ApplicationContextRef::ApplicationContext(ac),
"mechanical".to_owned(),
)?;
let pd_context = a.add_product_definition_context(
"part definition".to_owned(),
m::ApplicationContextRef::ApplicationContext(ac),
"design".to_owned(),
)?;
let length_prefix = match units.length {
LengthUnit::Millimetre => Some(m::SiPrefix::Milli),
LengthUnit::Metre => None,
};
let mm = a.add_complex(vec![
m::UnitPart::LengthUnit,
m::UnitPart::NamedUnit { dimensions: None },
m::UnitPart::SiUnit {
prefix: length_prefix,
name: m::SiUnitName::Metre,
},
])?;
let rad = a.add_complex(vec![
m::UnitPart::NamedUnit { dimensions: None },
m::UnitPart::PlaneAngleUnit,
m::UnitPart::SiUnit {
prefix: None,
name: m::SiUnitName::Radian,
},
])?;
let sr = a.add_complex(vec![
m::UnitPart::NamedUnit { dimensions: None },
m::UnitPart::SiUnit {
prefix: None,
name: m::SiUnitName::Steradian,
},
m::UnitPart::SolidAngleUnit,
])?;
let uncertainty = a.add_uncertainty_measure_with_unit(
m::MeasureValue {
type_name: Some("LENGTH_MEASURE".to_owned()),
value: m::MeasureScalar::Real(units.uncertainty),
},
m::UnitRef::Complex(mm),
"distance_accuracy_value".to_owned(),
Some("confusion accuracy".to_owned()),
)?;
let ctx = a.add_complex(vec![
m::UnitPart::GeometricRepresentationContext {
coordinate_space_dimension: 3,
},
m::UnitPart::GlobalUncertaintyAssignedContext {
uncertainty: vec![
m::UncertaintyMeasureWithUnitRef::UncertaintyMeasureWithUnit(uncertainty),
],
},
m::UnitPart::GlobalUnitAssignedContext {
units: vec![
m::UnitRef::Complex(mm),
m::UnitRef::Complex(rad),
m::UnitRef::Complex(sr),
],
},
m::UnitPart::RepresentationContext {
context_identifier: String::new(),
context_type: "3D".to_owned(),
},
])?;
Ok(Self {
author: a,
ctx,
product_context,
pd_context,
parts: Vec::new(),
placements: Vec::new(),
vertices: Vec::new(),
styles: Vec::new(),
hidden: Vec::new(),
header: HeaderInput::default(),
})
}
pub fn header(&mut self, h: &HeaderInput) {
self.header = h.clone();
}
pub fn author(&mut self) -> &mut Ap242Author {
&mut self.author
}
pub fn style(
&mut self,
target: StyleTarget,
color: Rgb,
transparency: Option<f64>,
) -> Result<m::StyledItemId, AuthorError> {
let a = &mut self.author;
let rgb = a.add_colour_rgb(String::new(), color.red, color.green, color.blue)?;
let element = if let Some(t) = transparency {
let transparent = a.add_surface_style_transparent(t)?;
let rendering = a.add_surface_style_rendering_with_properties(
m::ShadingSurfaceMethod::NormalShading,
m::ColourRef::ColourRgb(rgb),
vec![m::RenderingPropertiesSelectRef::SurfaceStyleTransparent(
transparent,
)],
)?;
m::SurfaceStyleElementSelectRef::SurfaceStyleRenderingWithProperties(rendering)
} else {
let fill_colour =
a.add_fill_area_style_colour(String::new(), m::ColourRef::ColourRgb(rgb))?;
let fill_style = a.add_fill_area_style(
String::new(),
vec![m::FillStyleSelectRef::FillAreaStyleColour(fill_colour)],
)?;
let fill_area =
a.add_surface_style_fill_area(m::FillAreaStyleRef::FillAreaStyle(fill_style))?;
m::SurfaceStyleElementSelectRef::SurfaceStyleFillArea(fill_area)
};
let side_style = a.add_surface_side_style(String::new(), vec![element])?;
let usage = a.add_surface_style_usage(
m::SurfaceSide::Both,
m::SurfaceSideStyleSelectRef::SurfaceSideStyle(side_style),
)?;
let assignment = a.add_presentation_style_assignment(vec![
m::PresentationStyleSelectRef::SurfaceStyleUsage(usage),
])?;
let item = match target {
StyleTarget::Face(f) => m::StyledItemTargetRef::AdvancedFace(f),
StyleTarget::Solid(s) => m::StyledItemTargetRef::ManifoldSolidBrep(s),
StyleTarget::VoidSolid(s) => m::StyledItemTargetRef::BrepWithVoids(s),
};
let styled = a.add_styled_item(
String::new(),
vec![m::PresentationStyleAssignmentRef::PresentationStyleAssignment(assignment)],
item,
)?;
self.styles.push(styled);
Ok(styled)
}
pub fn layer(
&mut self,
name: &str,
targets: Vec<StyleTarget>,
) -> Result<m::PresentationLayerAssignmentId, AuthorError> {
let items = targets
.into_iter()
.map(|t| match t {
StyleTarget::Face(f) => m::LayeredItemRef::AdvancedFace(f),
StyleTarget::Solid(s) => m::LayeredItemRef::ManifoldSolidBrep(s),
StyleTarget::VoidSolid(s) => m::LayeredItemRef::BrepWithVoids(s),
})
.collect();
self.author
.add_presentation_layer_assignment(name.to_owned(), String::new(), items)
}
pub fn hide(&mut self, target: StyleTarget) -> Result<(), AuthorError> {
let a = &mut self.author;
let assignment =
a.add_presentation_style_assignment(vec![m::PresentationStyleSelectRef::NullStyle(
m::NullStyle::Null,
)])?;
let item = match target {
StyleTarget::Face(f) => m::StyledItemTargetRef::AdvancedFace(f),
StyleTarget::Solid(s) => m::StyledItemTargetRef::ManifoldSolidBrep(s),
StyleTarget::VoidSolid(s) => m::StyledItemTargetRef::BrepWithVoids(s),
};
let styled = a.add_styled_item(
String::new(),
vec![m::PresentationStyleAssignmentRef::PresentationStyleAssignment(assignment)],
item,
)?;
self.styles.push(styled);
self.hidden.push(m::InvisibleItemRef::StyledItem(styled));
Ok(())
}
pub fn hide_layer(&mut self, layer: m::PresentationLayerAssignmentId) {
self.hidden
.push(m::InvisibleItemRef::PresentationLayerAssignment(layer));
}
fn placement(&mut self, f: &Frame) -> Result<m::Axis2Placement3dId, AuthorError> {
let a = &mut self.author;
let location = a.add_cartesian_point(String::new(), f.origin.to_vec())?;
let axis = a.add_direction(String::new(), f.axis.to_vec())?;
let ref_direction = a.add_direction(String::new(), f.ref_dir.to_vec())?;
a.add_axis2_placement3d(
String::new(),
m::CartesianPointRef::CartesianPoint(location),
Some(m::DirectionRef::Direction(axis)),
Some(m::DirectionRef::Direction(ref_direction)),
)
}
fn control_points(
&mut self,
row: &[[f64; 3]],
) -> Result<Vec<m::CartesianPointRef>, AuthorError> {
row.iter()
.map(|p| {
self.author
.add_cartesian_point(String::new(), p.to_vec())
.map(m::CartesianPointRef::CartesianPoint)
})
.collect()
}
fn profile_geometry(&mut self, profile: &ProfileInput) -> Result<m::CurveRef, AuthorError> {
match profile {
ProfileInput::Line(point, dir) => {
let a = &mut self.author;
let pnt = a.add_cartesian_point(String::new(), point.to_vec())?;
let orientation = a.add_direction(String::new(), dir.to_vec())?;
let vector =
a.add_vector(String::new(), m::DirectionRef::Direction(orientation), 1.0)?;
let line = a.add_line(
String::new(),
m::CartesianPointRef::CartesianPoint(pnt),
m::VectorRef::Vector(vector),
)?;
Ok(m::CurveRef::Line(line))
}
ProfileInput::Circle(frame, radius) => {
let position = self.placement(frame)?;
let circle = self.author.add_circle(
String::new(),
m::Axis2PlacementRef::Axis2Placement3d(position),
*radius,
)?;
Ok(m::CurveRef::Circle(circle))
}
ProfileInput::Ellipse(frame, semi_1, semi_2) => {
let position = self.placement(frame)?;
let ellipse = self.author.add_ellipse(
String::new(),
m::Axis2PlacementRef::Axis2Placement3d(position),
*semi_1,
*semi_2,
)?;
Ok(m::CurveRef::Ellipse(ellipse))
}
ProfileInput::Nurbs(curve) => self.nurbs_curve_geometry(curve),
}
}
#[allow(clippy::float_cmp)]
fn nurbs_curve_geometry(&mut self, c: &NurbsCurve) -> Result<m::CurveRef, AuthorError> {
let cps = self.control_points(&c.control_points)?;
let (knots, knot_multiplicities) = compress_knots(&c.knots);
let degree = i64::try_from(c.degree).unwrap_or(i64::MAX);
if c.weights.iter().all(|w| *w == 1.0) {
let id = self.author.add_b_spline_curve_with_knots(
String::new(),
degree,
cps,
m::BSplineCurveForm::Unspecified,
m::Logical::Unknown,
m::Logical::Unknown,
knot_multiplicities,
knots,
m::KnotType::Unspecified,
)?;
Ok(m::CurveRef::BSplineCurveWithKnots(id))
} else {
let id = self.author.add_complex(vec![
m::UnitPart::BoundedCurve,
m::UnitPart::BSplineCurve {
degree,
control_points_list: cps,
curve_form: m::BSplineCurveForm::Unspecified,
closed_curve: m::Logical::Unknown,
self_intersect: m::Logical::Unknown,
},
m::UnitPart::BSplineCurveWithKnots {
knot_multiplicities,
knots,
knot_spec: m::KnotType::Unspecified,
},
m::UnitPart::Curve,
m::UnitPart::GeometricRepresentationItem,
m::UnitPart::RationalBSplineCurve {
weights_data: c.weights.clone(),
},
m::UnitPart::RepresentationItem {
name: String::new(),
},
])?;
Ok(m::CurveRef::Complex(id))
}
}
#[allow(clippy::float_cmp)] fn nurbs_surface_geometry(&mut self, s: &NurbsSurface) -> Result<m::SurfaceRef, AuthorError> {
let mut cps = Vec::with_capacity(s.control_points.len());
for row in &s.control_points {
cps.push(self.control_points(row)?);
}
let (u_knots, u_multiplicities) = compress_knots(&s.knots_u);
let (v_knots, v_multiplicities) = compress_knots(&s.knots_v);
let u_degree = i64::try_from(s.degree_u).unwrap_or(i64::MAX);
let v_degree = i64::try_from(s.degree_v).unwrap_or(i64::MAX);
if s.weights.iter().flatten().all(|w| *w == 1.0) {
let id = self.author.add_b_spline_surface_with_knots(
String::new(),
u_degree,
v_degree,
cps,
m::BSplineSurfaceForm::Unspecified,
m::Logical::Unknown,
m::Logical::Unknown,
m::Logical::Unknown,
u_multiplicities,
v_multiplicities,
u_knots,
v_knots,
m::KnotType::Unspecified,
)?;
Ok(m::SurfaceRef::BSplineSurfaceWithKnots(id))
} else {
let id = self.author.add_complex(vec![
m::UnitPart::BoundedSurface,
m::UnitPart::BSplineSurface {
u_degree,
v_degree,
control_points_list: cps,
surface_form: m::BSplineSurfaceForm::Unspecified,
u_closed: m::Logical::Unknown,
v_closed: m::Logical::Unknown,
self_intersect: m::Logical::Unknown,
},
m::UnitPart::BSplineSurfaceWithKnots {
u_multiplicities,
v_multiplicities,
u_knots,
v_knots,
knot_spec: m::KnotType::Unspecified,
},
m::UnitPart::GeometricRepresentationItem,
m::UnitPart::RationalBSplineSurface {
weights_data: s.weights.clone(),
},
m::UnitPart::RepresentationItem {
name: String::new(),
},
m::UnitPart::Surface,
])?;
Ok(m::SurfaceRef::Complex(id))
}
}
pub fn vertex(&mut self, p: [f64; 3]) -> Result<Vertex, AuthorError> {
let point = self.author.add_cartesian_point(String::new(), p.to_vec())?;
let vp = self
.author
.add_vertex_point(String::new(), m::PointRef::CartesianPoint(point))?;
self.vertices.push((vp, p));
Ok(Vertex(self.vertices.len() - 1))
}
pub fn edge(
&mut self,
from: Vertex,
to: Vertex,
curve: CurveInput,
) -> Result<m::EdgeCurveId, AuthorError> {
let (start_vertex, start) = self.vertices[from.0];
let (end_vertex, end) = self.vertices[to.0];
let geometry = match curve {
CurveInput::Line => {
let delta = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
let len = (delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]).sqrt();
let dir = if len < 1e-12 {
[0.0, 0.0, 1.0]
} else {
[delta[0] / len, delta[1] / len, delta[2] / len]
};
let a = &mut self.author;
let pnt = a.add_cartesian_point(String::new(), start.to_vec())?;
let orientation = a.add_direction(String::new(), dir.to_vec())?;
let vector =
a.add_vector(String::new(), m::DirectionRef::Direction(orientation), len)?;
let line = a.add_line(
String::new(),
m::CartesianPointRef::CartesianPoint(pnt),
m::VectorRef::Vector(vector),
)?;
m::CurveRef::Line(line)
}
CurveInput::Circle(frame, radius) => {
let position = self.placement(&frame)?;
let circle = self.author.add_circle(
String::new(),
m::Axis2PlacementRef::Axis2Placement3d(position),
radius,
)?;
m::CurveRef::Circle(circle)
}
CurveInput::Ellipse(frame, semi_1, semi_2) => {
self.profile_geometry(&ProfileInput::Ellipse(frame, semi_1, semi_2))?
}
CurveInput::Polyline(points) => {
let refs = self.control_points(&points)?;
m::CurveRef::Polyline(self.author.add_polyline(String::new(), refs)?)
}
CurveInput::Nurbs(curve) => self.nurbs_curve_geometry(&curve)?,
};
self.author.add_edge_curve(
String::new(),
m::VertexRef::VertexPoint(start_vertex),
m::VertexRef::VertexPoint(end_vertex),
geometry,
true,
)
}
fn surface_geometry(&mut self, surface: SurfaceInput) -> Result<m::SurfaceRef, AuthorError> {
Ok(match surface {
SurfaceInput::Plane(frame) => {
let position = self.placement(&frame)?;
let plane = self.author.add_plane(
String::new(),
m::Axis2Placement3dRef::Axis2Placement3d(position),
)?;
m::SurfaceRef::Plane(plane)
}
SurfaceInput::Cylinder(frame, radius) => {
let position = self.placement(&frame)?;
let cyl = self.author.add_cylindrical_surface(
String::new(),
m::Axis2Placement3dRef::Axis2Placement3d(position),
radius,
)?;
m::SurfaceRef::CylindricalSurface(cyl)
}
SurfaceInput::Sphere(frame, radius) => {
let position = self.placement(&frame)?;
let sphere = self.author.add_spherical_surface(
String::new(),
m::Axis2Placement3dRef::Axis2Placement3d(position),
radius,
)?;
m::SurfaceRef::SphericalSurface(sphere)
}
SurfaceInput::Torus(frame, major_radius, minor_radius) => {
let position = self.placement(&frame)?;
let torus = self.author.add_toroidal_surface(
String::new(),
m::Axis2Placement3dRef::Axis2Placement3d(position),
major_radius,
minor_radius,
)?;
m::SurfaceRef::ToroidalSurface(torus)
}
SurfaceInput::Cone(frame, radius, semi_angle) => {
let position = self.placement(&frame)?;
let cone = self.author.add_conical_surface(
String::new(),
m::Axis2Placement3dRef::Axis2Placement3d(position),
radius,
semi_angle,
)?;
m::SurfaceRef::ConicalSurface(cone)
}
SurfaceInput::LinearExtrusion(profile, sweep) => {
let swept_curve = self.profile_geometry(&profile)?;
let len = (sweep[0] * sweep[0] + sweep[1] * sweep[1] + sweep[2] * sweep[2]).sqrt();
let dir = if len < 1e-12 {
[0.0, 0.0, 1.0]
} else {
[sweep[0] / len, sweep[1] / len, sweep[2] / len]
};
let a = &mut self.author;
let orientation = a.add_direction(String::new(), dir.to_vec())?;
let vector =
a.add_vector(String::new(), m::DirectionRef::Direction(orientation), len)?;
let surf = a.add_surface_of_linear_extrusion(
String::new(),
swept_curve,
m::VectorRef::Vector(vector),
)?;
m::SurfaceRef::SurfaceOfLinearExtrusion(surf)
}
SurfaceInput::Revolution(profile, origin, axis) => {
let swept_curve = self.profile_geometry(&profile)?;
let a = &mut self.author;
let location = a.add_cartesian_point(String::new(), origin.to_vec())?;
let axis_dir = a.add_direction(String::new(), axis.to_vec())?;
let position = a.add_axis1_placement(
String::new(),
m::CartesianPointRef::CartesianPoint(location),
Some(m::DirectionRef::Direction(axis_dir)),
)?;
let surf = a.add_surface_of_revolution(
String::new(),
swept_curve,
m::Axis1PlacementRef::Axis1Placement(position),
)?;
m::SurfaceRef::SurfaceOfRevolution(surf)
}
SurfaceInput::Nurbs(surface) => self.nurbs_surface_geometry(&surface)?,
})
}
pub fn face(
&mut self,
surface: SurfaceInput,
same_sense: bool,
bounds: Vec<FaceBoundInput>,
) -> Result<m::AdvancedFaceId, AuthorError> {
let face_geometry = self.surface_geometry(surface)?;
let mut face_bounds = Vec::with_capacity(bounds.len());
for bound in bounds {
let mut edge_list = Vec::with_capacity(bound.edges.len());
for (edge, forward) in bound.edges {
let oe = self.author.add_oriented_edge(
String::new(),
m::EdgeRef::EdgeCurve(edge),
forward,
)?;
edge_list.push(m::OrientedEdgeRef::OrientedEdge(oe));
}
let el = self.author.add_edge_loop(String::new(), edge_list)?;
let fb = if bound.outer {
m::FaceBoundRef::FaceOuterBound(self.author.add_face_outer_bound(
String::new(),
m::LoopRef::EdgeLoop(el),
bound.orientation,
)?)
} else {
m::FaceBoundRef::FaceBound(self.author.add_face_bound(
String::new(),
m::LoopRef::EdgeLoop(el),
bound.orientation,
)?)
};
face_bounds.push(fb);
}
self.author
.add_advanced_face(String::new(), face_bounds, face_geometry, same_sense)
}
pub fn solid(
&mut self,
part: Part,
name: &str,
faces: Vec<m::AdvancedFaceId>,
) -> Result<m::ManifoldSolidBrepId, AuthorError> {
let shell = self.author.add_closed_shell(
String::new(),
faces.into_iter().map(m::FaceRef::AdvancedFace).collect(),
)?;
let solid = self
.author
.add_manifold_solid_brep(name.to_owned(), m::ClosedShellRef::ClosedShell(shell))?;
self.parts[part.0]
.items
.push(m::RepresentationItemRef::ManifoldSolidBrep(solid));
Ok(solid)
}
pub fn solid_with_voids(
&mut self,
part: Part,
name: &str,
outer_faces: Vec<m::AdvancedFaceId>,
voids: Vec<Vec<m::AdvancedFaceId>>,
normals: VoidShellNormals,
) -> Result<m::BrepWithVoidsId, AuthorError> {
let outer = self.author.add_closed_shell(
String::new(),
outer_faces
.into_iter()
.map(m::FaceRef::AdvancedFace)
.collect(),
)?;
let orientation = match normals {
VoidShellNormals::AwayFromMaterial => true, VoidShellNormals::TowardMaterial => false, };
let mut void_refs = Vec::with_capacity(voids.len());
for void_faces in voids {
let shell = self.author.add_closed_shell(
String::new(),
void_faces
.into_iter()
.map(m::FaceRef::AdvancedFace)
.collect(),
)?;
let oriented = self.author.add_oriented_closed_shell(
String::new(),
m::ClosedShellRef::ClosedShell(shell),
orientation,
)?;
void_refs.push(m::OrientedClosedShellRef::OrientedClosedShell(oriented));
}
let solid = self.author.add_brep_with_voids(
name.to_owned(),
m::ClosedShellRef::ClosedShell(outer),
void_refs,
)?;
self.parts[part.0]
.items
.push(m::RepresentationItemRef::BrepWithVoids(solid));
Ok(solid)
}
pub fn part(&mut self, name: &str) -> Result<Part, AuthorError> {
self.part_with(name, &PartOptions::default())
}
pub fn part_with(&mut self, name: &str, opts: &PartOptions) -> Result<Part, AuthorError> {
let origin = self.placement(&Frame {
origin: [0.0; 3],
axis: [0.0, 0.0, 1.0],
ref_dir: [1.0, 0.0, 0.0],
})?;
let a = &mut self.author;
let product = a.add_product(
opts.id.clone().unwrap_or_else(|| name.to_owned()),
name.to_owned(),
opts.description.clone(),
vec![m::ProductContextRef::ProductContext(self.product_context)],
)?;
let formation = a.add_product_definition_formation(
opts.version.clone().unwrap_or_default(),
opts.version_description.clone(),
m::ProductRef::Product(product),
)?;
let definition = a.add_product_definition(
"design".to_owned(),
opts.definition_description.clone(),
m::ProductDefinitionFormationRef::ProductDefinitionFormation(formation),
m::ProductDefinitionContextRef::ProductDefinitionContext(self.pd_context),
)?;
let shape = a.add_product_definition_shape(
String::new(),
None,
m::CharacterizedDefinitionRef::ProductDefinition(definition),
)?;
self.parts.push(PendingPart {
product,
formation,
definition,
shape,
origin,
items: Vec::new(),
meshes: Vec::new(),
});
Ok(Part(self.parts.len() - 1))
}
pub fn mesh(
&mut self,
part: Part,
name: &str,
input: &MeshInput,
of_solid: Option<SolidRef>,
) -> Result<m::TessellatedSolidId, AuthorError> {
let a = &mut self.author;
let npoints = i64::try_from(input.points.len()).unwrap_or(i64::MAX);
let coordinates = a.add_coordinates_list(
String::new(),
npoints,
input.points.iter().map(|p| p.to_vec()).collect(),
)?;
let normals: Vec<Vec<f64>> = match &input.normals {
MeshNormalsInput::None => Vec::new(),
MeshNormalsInput::Uniform(n) => vec![n.to_vec()],
MeshNormalsInput::PerVertex(rows) => rows.iter().map(|n| n.to_vec()).collect(),
};
let to_ix = |v: usize| i64::try_from(v + 1).unwrap_or(i64::MAX);
let triangle_strips: Vec<Vec<i64>> = input
.triangles
.iter()
.map(|&[p, q, r]| vec![to_ix(p), to_ix(r), to_ix(q)])
.collect();
let face = a.add_complex_triangulated_face(
name.to_owned(),
m::CoordinatesListRef::CoordinatesList(coordinates),
npoints,
normals,
None,
Vec::new(),
triangle_strips,
Vec::new(),
)?;
let solid = a.add_tessellated_solid(
name.to_owned(),
vec![m::TessellatedStructuredItemRef::ComplexTriangulatedFace(
face,
)],
of_solid.map(|s| match s {
SolidRef::Solid(id) => m::ManifoldSolidBrepRef::ManifoldSolidBrep(id),
SolidRef::VoidSolid(id) => m::ManifoldSolidBrepRef::BrepWithVoids(id),
}),
)?;
self.parts[part.0].meshes.push(solid);
Ok(solid)
}
pub fn person_and_org(
&mut self,
p: &PersonOrg,
) -> Result<m::PersonAndOrganizationId, AuthorError> {
let a = &mut self.author;
let person = a.add_person(
p.person_id.clone(),
p.last_name.clone(),
p.first_name.clone(),
None,
None,
None,
)?;
let organization = a.add_organization(None, p.organization.clone(), None)?;
a.add_person_and_organization(
m::PersonRef::Person(person),
m::OrganizationRef::Organization(organization),
)
}
pub fn contributor(
&mut self,
part: Part,
role: &str,
who: m::PersonAndOrganizationId,
) -> Result<(), AuthorError> {
let a = &mut self.author;
let role = a.add_person_and_organization_role(role.to_owned())?;
a.add_applied_person_and_organization_assignment(
m::PersonAndOrganizationRef::PersonAndOrganization(who),
m::PersonAndOrganizationRoleRef::PersonAndOrganizationRole(role),
vec![m::PersonAndOrganizationItemRef::ProductDefinitionFormation(
self.parts[part.0].formation,
)],
)?;
Ok(())
}
pub fn approve(
&mut self,
part: Part,
input: &ApprovalInput,
) -> Result<m::ApprovalId, AuthorError> {
let a = &mut self.author;
let status = a.add_approval_status(input.status.clone())?;
let approval = a.add_approval(
m::ApprovalStatusRef::ApprovalStatus(status),
input.level.clone(),
)?;
a.add_applied_approval_assignment(
m::ApprovalRef::Approval(approval),
vec![m::ApprovalItemRef::ProductDefinitionFormation(
self.parts[part.0].formation,
)],
)?;
for (who, role) in &input.approvers {
let role = a.add_approval_role(role.clone())?;
a.add_approval_person_organization(
m::PersonOrganizationSelectRef::PersonAndOrganization(*who),
m::ApprovalRef::Approval(approval),
m::ApprovalRoleRef::ApprovalRole(role),
)?;
}
if let Some(date) = input.date {
let calendar = a.add_calendar_date(date.year, date.day, date.month)?;
let zone = a.add_coordinated_universal_time_offset(0, None, m::AheadOrBehind::Exact)?;
let time = a.add_local_time(
date.hour,
date.minute,
None,
m::CoordinatedUniversalTimeOffsetRef::CoordinatedUniversalTimeOffset(zone),
)?;
let date_time = a.add_date_and_time(
m::DateRef::CalendarDate(calendar),
m::LocalTimeRef::LocalTime(time),
)?;
a.add_approval_date_time(
m::DateTimeSelectRef::DateAndTime(date_time),
m::ApprovalRef::Approval(approval),
)?;
}
Ok(approval)
}
pub fn document(&mut self, part: Part, d: &DocumentInput) -> Result<(), AuthorError> {
let a = &mut self.author;
let kind = a.add_document_type(d.kind.clone())?;
let doc = a.add_document(
d.id.clone(),
d.name.clone(),
d.description.clone(),
m::DocumentTypeRef::DocumentType(kind),
)?;
a.add_applied_document_reference(
m::DocumentRef::Document(doc),
String::new(),
vec![m::DocumentReferenceItemRef::ProductDefinitionFormation(
self.parts[part.0].formation,
)],
)?;
Ok(())
}
pub fn security(
&mut self,
part: Part,
name: &str,
purpose: &str,
level: &str,
) -> Result<(), AuthorError> {
let a = &mut self.author;
let level = a.add_security_classification_level(level.to_owned())?;
let classification = a.add_security_classification(
name.to_owned(),
purpose.to_owned(),
m::SecurityClassificationLevelRef::SecurityClassificationLevel(level),
)?;
a.add_applied_security_classification_assignment(
m::SecurityClassificationRef::SecurityClassification(classification),
vec![
m::SecurityClassificationItemRef::ProductDefinitionFormation(
self.parts[part.0].formation,
),
],
)?;
Ok(())
}
pub fn place(
&mut self,
parent: Part,
child: Part,
at: Frame,
) -> Result<m::NextAssemblyUsageOccurrenceId, AuthorError> {
let nauo = self.author.add_next_assembly_usage_occurrence(
format!("{}", self.placements.len() + 1),
String::new(),
None,
m::ProductDefinitionOrReferenceRef::ProductDefinition(self.parts[parent.0].definition),
m::ProductDefinitionOrReferenceRef::ProductDefinition(self.parts[child.0].definition),
None,
)?;
let to = self.placement(&at)?;
self.placements.push(PendingPlacement {
nauo,
parent: parent.0,
child: child.0,
to,
});
Ok(nauo)
}
pub fn finish(mut self) -> Result<String, AuthorError> {
let a = &mut self.author;
let mut part_reps: Vec<m::RepresentationOrRepresentationReferenceRef> =
Vec::with_capacity(self.parts.len());
for part in &self.parts {
let mut items = vec![m::RepresentationItemRef::Axis2Placement3d(part.origin)];
items.extend(part.items.iter().cloned());
let has_solid = part.items.iter().any(|i| {
matches!(
i,
m::RepresentationItemRef::ManifoldSolidBrep(_)
| m::RepresentationItemRef::BrepWithVoids(_)
)
});
let (rep, rep_or_ref) = if has_solid {
let id = a.add_advanced_brep_shape_representation(
String::new(),
items,
m::RepresentationContextRef::Complex(self.ctx),
)?;
(
m::RepresentationRef::AdvancedBrepShapeRepresentation(id),
m::RepresentationOrRepresentationReferenceRef::AdvancedBrepShapeRepresentation(
id,
),
)
} else {
let id = a.add_shape_representation(
String::new(),
items,
m::RepresentationContextRef::Complex(self.ctx),
)?;
(
m::RepresentationRef::ShapeRepresentation(id),
m::RepresentationOrRepresentationReferenceRef::ShapeRepresentation(id),
)
};
a.add_shape_definition_representation(
m::RepresentedDefinitionRef::ProductDefinitionShape(part.shape),
rep,
)?;
part_reps.push(rep_or_ref);
if !part.meshes.is_empty() {
let tsr = a.add_tessellated_shape_representation(
String::new(),
part.meshes
.iter()
.map(|&t| m::RepresentationItemRef::TessellatedSolid(t))
.collect(),
m::RepresentationContextRef::Complex(self.ctx),
)?;
a.add_shape_definition_representation(
m::RepresentedDefinitionRef::ProductDefinitionShape(part.shape),
m::RepresentationRef::TessellatedShapeRepresentation(tsr),
)?;
}
}
bind_placements(a, &self.parts, &self.placements, &part_reps)?;
if !self.parts.is_empty() {
a.add_product_related_product_category(
"part".to_owned(),
None,
self.parts
.iter()
.map(|p| m::ProductRef::Product(p.product))
.collect(),
)?;
}
if !self.hidden.is_empty() {
a.add_invisibility(std::mem::take(&mut self.hidden))?;
}
if !self.styles.is_empty() {
a.add_mechanical_design_geometric_presentation_representation(
String::new(),
self.styles
.iter()
.map(|&s| m::RepresentationItemRef::StyledItem(s))
.collect(),
m::RepresentationContextRef::Complex(self.ctx),
)?;
}
let h = &self.header;
let file_header = FileHeader {
description: h.description.clone().unwrap_or_default(),
file_name: h.file_name.clone().unwrap_or_default(),
time_stamp: h.timestamp.clone().unwrap_or_else(now_iso_utc),
authors: h.authors.clone(),
organizations: h.organizations.clone(),
preprocessor_version: concat!("step-io ", env!("CARGO_PKG_VERSION")).to_owned(),
originating_system: h.originating_system.clone().unwrap_or_default(),
authorisation: h.authorisation.clone().unwrap_or_default(),
schema: crate::parser::SchemaId::default(),
};
Ok(self.author.finish_with_header(&file_header))
}
}
fn bind_placements(
a: &mut Ap242Author,
parts: &[PendingPart],
placements: &[PendingPlacement],
part_reps: &[m::RepresentationOrRepresentationReferenceRef],
) -> Result<(), AuthorError> {
for placement in placements {
let pds = a.add_product_definition_shape(
"Placement".to_owned(),
Some("Placement of an item".to_owned()),
m::CharacterizedDefinitionRef::NextAssemblyUsageOccurrence(placement.nauo),
)?;
let idt = a.add_item_defined_transformation(
String::new(),
None,
m::RepresentationItemRef::Axis2Placement3d(parts[placement.parent].origin),
m::RepresentationItemRef::Axis2Placement3d(placement.to),
)?;
let rrwt = a.add_complex(vec![
m::UnitPart::RepresentationRelationship {
name: String::new(),
description: None,
rep_1: part_reps[placement.child].clone(),
rep_2: part_reps[placement.parent].clone(),
},
m::UnitPart::RepresentationRelationshipWithTransformation {
transformation_operator: m::TransformationRef::ItemDefinedTransformation(idt),
},
m::UnitPart::ShapeRepresentationRelationship,
])?;
a.add_context_dependent_shape_representation(
m::ShapeRepresentationRelationshipRef::Complex(rrwt),
m::ProductDefinitionShapeRef::ProductDefinitionShape(pds),
)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::iso_utc_from_unix;
#[test]
fn iso_utc_from_unix_known_values() {
assert_eq!(iso_utc_from_unix(0), "1970-01-01T00:00:00Z");
assert_eq!(iso_utc_from_unix(1_709_251_199), "2024-02-29T23:59:59Z");
assert_eq!(iso_utc_from_unix(1_709_251_200), "2024-03-01T00:00:00Z");
}
}