1use crate::generated::author::{Ap242Author, AuthorError};
133use crate::generated::model as m;
134use crate::header::FileHeader;
135pub use crate::scene::{NurbsCurve, NurbsSurface, Rgb};
136
137#[derive(Debug, Clone, Default)]
142pub struct HeaderInput {
143 pub file_name: Option<String>,
145 pub description: Option<String>,
147 pub authors: Vec<String>,
148 pub organizations: Vec<String>,
149 pub originating_system: Option<String>,
151 pub authorisation: Option<String>,
152 pub timestamp: Option<String>,
155}
156
157fn iso_utc_from_unix(secs: u64) -> String {
160 let days = secs / 86_400;
161 let rem = secs % 86_400;
162 let (hour, minute, second) = (rem / 3600, (rem % 3600) / 60, rem % 60);
163 let z = days + 719_468;
164 let era = z / 146_097;
165 let doe = z % 146_097;
166 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
167 let year = yoe + era * 400;
168 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
169 let mp = (5 * doy + 2) / 153;
170 let day = doy - (153 * mp + 2) / 5 + 1;
171 let month = if mp < 10 { mp + 3 } else { mp - 9 };
172 let year = if month <= 2 { year + 1 } else { year };
173 format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z")
174}
175
176fn now_iso_utc() -> String {
178 let secs = std::time::SystemTime::now()
179 .duration_since(std::time::UNIX_EPOCH)
180 .map_or(0, |d| d.as_secs());
181 iso_utc_from_unix(secs)
182}
183
184#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
187pub enum LengthUnit {
188 #[default]
189 Millimetre,
190 Metre,
191}
192
193#[derive(Debug, Clone, Copy)]
196pub struct UnitsInput {
197 pub length: LengthUnit,
198 pub uncertainty: f64,
202}
203
204impl Default for UnitsInput {
205 fn default() -> Self {
206 Self {
207 length: LengthUnit::Millimetre,
208 uncertainty: 1.0e-7,
209 }
210 }
211}
212
213#[derive(Debug, Clone, Copy)]
219pub struct Frame {
220 pub origin: [f64; 3],
221 pub axis: [f64; 3],
222 pub ref_dir: [f64; 3],
223}
224
225#[derive(Debug, Clone)]
232pub enum SurfaceInput {
233 Plane(Frame),
234 Cylinder(Frame, f64),
236 Sphere(Frame, f64),
238 Torus(Frame, f64, f64),
240 Cone(Frame, f64, f64),
243 LinearExtrusion(ProfileInput, [f64; 3]),
245 Revolution(ProfileInput, [f64; 3], [f64; 3]),
247 Nurbs(NurbsSurface),
248}
249
250#[derive(Debug, Clone)]
254pub enum CurveInput {
255 Line,
257 Circle(Frame, f64),
260 Ellipse(Frame, f64, f64),
263 Polyline(Vec<[f64; 3]>),
266 Nurbs(NurbsCurve),
267}
268
269#[derive(Debug, Clone)]
272pub enum ProfileInput {
273 Line([f64; 3], [f64; 3]),
275 Circle(Frame, f64),
276 Ellipse(Frame, f64, f64),
277 Nurbs(NurbsCurve),
278}
279
280#[derive(Debug, Clone)]
284pub struct FaceBoundInput {
285 pub edges: Vec<(m::EdgeCurveId, bool)>,
286 pub outer: bool,
287 pub orientation: bool,
288}
289
290impl FaceBoundInput {
291 #[must_use]
293 pub fn outer(edges: Vec<(m::EdgeCurveId, bool)>) -> Self {
294 Self {
295 edges,
296 outer: true,
297 orientation: true,
298 }
299 }
300
301 #[must_use]
303 pub fn inner(edges: Vec<(m::EdgeCurveId, bool)>) -> Self {
304 Self {
305 edges,
306 outer: false,
307 orientation: true,
308 }
309 }
310}
311
312fn compress_knots(expanded: &[f64]) -> (Vec<f64>, Vec<i64>) {
317 let mut knots: Vec<f64> = Vec::new();
318 let mut multiplicities: Vec<i64> = Vec::new();
319 for &k in expanded {
320 if knots.last() == Some(&k) {
321 *multiplicities.last_mut().unwrap() += 1;
322 } else {
323 knots.push(k);
324 multiplicities.push(1);
325 }
326 }
327 (knots, multiplicities)
328}
329
330#[derive(Debug, Clone, Default)]
333pub struct PartOptions {
334 pub id: Option<String>,
336 pub description: Option<String>,
338 pub version: Option<String>,
340 pub version_description: Option<String>,
342 pub definition_description: Option<String>,
344}
345
346#[derive(Debug, Clone)]
348pub struct PersonOrg {
349 pub person_id: String,
350 pub first_name: Option<String>,
351 pub last_name: Option<String>,
352 pub organization: String,
353}
354
355#[derive(Debug, Clone, Copy)]
357pub struct DateTimeInput {
358 pub year: i64,
359 pub month: i64,
360 pub day: i64,
361 pub hour: i64,
362 pub minute: Option<i64>,
363}
364
365#[derive(Debug, Clone, Default)]
369pub struct ApprovalInput {
370 pub status: String,
371 pub level: String,
372 pub approvers: Vec<(m::PersonAndOrganizationId, String)>,
373 pub date: Option<DateTimeInput>,
374}
375
376#[derive(Debug, Clone)]
378pub struct DocumentInput {
379 pub id: String,
380 pub name: String,
381 pub description: Option<String>,
382 pub kind: String,
384}
385
386#[derive(Debug, Clone, Default)]
390pub enum MeshNormalsInput {
391 #[default]
392 None,
393 Uniform([f64; 3]),
394 PerVertex(Vec<[f64; 3]>),
395}
396
397#[derive(Debug, Clone)]
400pub struct MeshInput {
401 pub points: Vec<[f64; 3]>,
402 pub triangles: Vec<[usize; 3]>,
403 pub normals: MeshNormalsInput,
404}
405
406#[derive(Debug, Clone, Copy)]
409pub enum StyleTarget {
410 Face(m::AdvancedFaceId),
411 Solid(m::ManifoldSolidBrepId),
412 VoidSolid(m::BrepWithVoidsId),
414}
415
416#[derive(Debug, Clone, Copy)]
421pub enum SolidRef {
422 Solid(m::ManifoldSolidBrepId),
423 VoidSolid(m::BrepWithVoidsId),
424}
425
426impl From<m::ManifoldSolidBrepId> for SolidRef {
427 fn from(id: m::ManifoldSolidBrepId) -> Self {
428 SolidRef::Solid(id)
429 }
430}
431
432impl From<m::BrepWithVoidsId> for SolidRef {
433 fn from(id: m::BrepWithVoidsId) -> Self {
434 SolidRef::VoidSolid(id)
435 }
436}
437
438#[derive(Debug, Copy, Clone, PartialEq, Eq)]
443pub struct Vertex(usize);
444
445#[derive(Debug, Copy, Clone, PartialEq, Eq)]
449pub struct Part(usize);
450
451struct PendingPart {
455 product: m::ProductId,
456 formation: m::ProductDefinitionFormationId,
457 definition: m::ProductDefinitionId,
458 shape: m::ProductDefinitionShapeId,
459 origin: m::Axis2Placement3dId,
460 items: Vec<m::RepresentationItemRef>,
461 meshes: Vec<m::TessellatedSolidId>,
462}
463
464struct PendingPlacement {
469 nauo: m::NextAssemblyUsageOccurrenceId,
470 parent: usize,
471 child: usize,
472 to: m::Axis2Placement3dId,
473}
474
475pub struct StepBuilder {
479 author: Ap242Author,
480 ctx: m::ComplexUnitId,
481 product_context: m::ProductContextId,
482 pd_context: m::ProductDefinitionContextId,
483 parts: Vec<PendingPart>,
484 placements: Vec<PendingPlacement>,
485 vertices: Vec<(m::VertexPointId, [f64; 3])>,
486 styles: Vec<m::StyledItemId>,
487 hidden: Vec<m::InvisibleItemRef>,
488 header: HeaderInput,
489}
490
491impl StepBuilder {
492 pub fn new() -> Result<Self, AuthorError> {
499 Self::new_with(&UnitsInput::default())
500 }
501
502 pub fn new_with(units: &UnitsInput) -> Result<Self, AuthorError> {
511 let mut a = Ap242Author::new();
512
513 let ac = a.add_application_context("managed model based 3d engineering".to_owned())?;
514 a.add_application_protocol_definition(
515 "international standard".to_owned(),
516 "ap242_managed_model_based_3d_engineering_mim_lf".to_owned(),
517 2011,
518 m::ApplicationContextRef::ApplicationContext(ac),
519 )?;
520 let product_context = a.add_product_context(
521 String::new(),
522 m::ApplicationContextRef::ApplicationContext(ac),
523 "mechanical".to_owned(),
524 )?;
525 let pd_context = a.add_product_definition_context(
526 "part definition".to_owned(),
527 m::ApplicationContextRef::ApplicationContext(ac),
528 "design".to_owned(),
529 )?;
530
531 let length_prefix = match units.length {
532 LengthUnit::Millimetre => Some(m::SiPrefix::Milli),
533 LengthUnit::Metre => None,
534 };
535 let mm = a.add_complex(vec![
536 m::UnitPart::LengthUnit,
537 m::UnitPart::NamedUnit { dimensions: None },
538 m::UnitPart::SiUnit {
539 prefix: length_prefix,
540 name: m::SiUnitName::Metre,
541 },
542 ])?;
543 let rad = a.add_complex(vec![
544 m::UnitPart::NamedUnit { dimensions: None },
545 m::UnitPart::PlaneAngleUnit,
546 m::UnitPart::SiUnit {
547 prefix: None,
548 name: m::SiUnitName::Radian,
549 },
550 ])?;
551 let sr = a.add_complex(vec![
552 m::UnitPart::NamedUnit { dimensions: None },
553 m::UnitPart::SiUnit {
554 prefix: None,
555 name: m::SiUnitName::Steradian,
556 },
557 m::UnitPart::SolidAngleUnit,
558 ])?;
559 let uncertainty = a.add_uncertainty_measure_with_unit(
560 m::MeasureValue {
561 type_name: Some("LENGTH_MEASURE".to_owned()),
562 value: m::MeasureScalar::Real(units.uncertainty),
563 },
564 m::UnitRef::Complex(mm),
565 "distance_accuracy_value".to_owned(),
566 Some("confusion accuracy".to_owned()),
567 )?;
568 let ctx = a.add_complex(vec![
569 m::UnitPart::GeometricRepresentationContext {
570 coordinate_space_dimension: 3,
571 },
572 m::UnitPart::GlobalUncertaintyAssignedContext {
573 uncertainty: vec![
574 m::UncertaintyMeasureWithUnitRef::UncertaintyMeasureWithUnit(uncertainty),
575 ],
576 },
577 m::UnitPart::GlobalUnitAssignedContext {
578 units: vec![
579 m::UnitRef::Complex(mm),
580 m::UnitRef::Complex(rad),
581 m::UnitRef::Complex(sr),
582 ],
583 },
584 m::UnitPart::RepresentationContext {
585 context_identifier: String::new(),
586 context_type: "3D".to_owned(),
587 },
588 ])?;
589
590 Ok(Self {
591 author: a,
592 ctx,
593 product_context,
594 pd_context,
595 parts: Vec::new(),
596 placements: Vec::new(),
597 vertices: Vec::new(),
598 styles: Vec::new(),
599 hidden: Vec::new(),
600 header: HeaderInput::default(),
601 })
602 }
603
604 pub fn header(&mut self, h: &HeaderInput) {
610 self.header = h.clone();
611 }
612
613 pub fn author(&mut self) -> &mut Ap242Author {
624 &mut self.author
625 }
626
627 pub fn style(
636 &mut self,
637 target: StyleTarget,
638 color: Rgb,
639 transparency: Option<f64>,
640 ) -> Result<m::StyledItemId, AuthorError> {
641 let a = &mut self.author;
642 let rgb = a.add_colour_rgb(String::new(), color.red, color.green, color.blue)?;
643 let element = if let Some(t) = transparency {
646 let transparent = a.add_surface_style_transparent(t)?;
647 let rendering = a.add_surface_style_rendering_with_properties(
648 m::ShadingSurfaceMethod::NormalShading,
649 m::ColourRef::ColourRgb(rgb),
650 vec![m::RenderingPropertiesSelectRef::SurfaceStyleTransparent(
651 transparent,
652 )],
653 )?;
654 m::SurfaceStyleElementSelectRef::SurfaceStyleRenderingWithProperties(rendering)
655 } else {
656 let fill_colour =
657 a.add_fill_area_style_colour(String::new(), m::ColourRef::ColourRgb(rgb))?;
658 let fill_style = a.add_fill_area_style(
659 String::new(),
660 vec![m::FillStyleSelectRef::FillAreaStyleColour(fill_colour)],
661 )?;
662 let fill_area =
663 a.add_surface_style_fill_area(m::FillAreaStyleRef::FillAreaStyle(fill_style))?;
664 m::SurfaceStyleElementSelectRef::SurfaceStyleFillArea(fill_area)
665 };
666 let side_style = a.add_surface_side_style(String::new(), vec![element])?;
667 let usage = a.add_surface_style_usage(
668 m::SurfaceSide::Both,
669 m::SurfaceSideStyleSelectRef::SurfaceSideStyle(side_style),
670 )?;
671 let assignment = a.add_presentation_style_assignment(vec![
672 m::PresentationStyleSelectRef::SurfaceStyleUsage(usage),
673 ])?;
674 let item = match target {
675 StyleTarget::Face(f) => m::StyledItemTargetRef::AdvancedFace(f),
676 StyleTarget::Solid(s) => m::StyledItemTargetRef::ManifoldSolidBrep(s),
677 StyleTarget::VoidSolid(s) => m::StyledItemTargetRef::BrepWithVoids(s),
678 };
679 let styled = a.add_styled_item(
680 String::new(),
681 vec![m::PresentationStyleAssignmentRef::PresentationStyleAssignment(assignment)],
682 item,
683 )?;
684 self.styles.push(styled);
685 Ok(styled)
686 }
687
688 pub fn layer(
697 &mut self,
698 name: &str,
699 targets: Vec<StyleTarget>,
700 ) -> Result<m::PresentationLayerAssignmentId, AuthorError> {
701 let items = targets
702 .into_iter()
703 .map(|t| match t {
704 StyleTarget::Face(f) => m::LayeredItemRef::AdvancedFace(f),
705 StyleTarget::Solid(s) => m::LayeredItemRef::ManifoldSolidBrep(s),
706 StyleTarget::VoidSolid(s) => m::LayeredItemRef::BrepWithVoids(s),
707 })
708 .collect();
709 self.author
710 .add_presentation_layer_assignment(name.to_owned(), String::new(), items)
711 }
712
713 pub fn hide(&mut self, target: StyleTarget) -> Result<(), AuthorError> {
723 let a = &mut self.author;
724 let assignment =
725 a.add_presentation_style_assignment(vec![m::PresentationStyleSelectRef::NullStyle(
726 m::NullStyle::Null,
727 )])?;
728 let item = match target {
729 StyleTarget::Face(f) => m::StyledItemTargetRef::AdvancedFace(f),
730 StyleTarget::Solid(s) => m::StyledItemTargetRef::ManifoldSolidBrep(s),
731 StyleTarget::VoidSolid(s) => m::StyledItemTargetRef::BrepWithVoids(s),
732 };
733 let styled = a.add_styled_item(
734 String::new(),
735 vec![m::PresentationStyleAssignmentRef::PresentationStyleAssignment(assignment)],
736 item,
737 )?;
738 self.styles.push(styled);
739 self.hidden.push(m::InvisibleItemRef::StyledItem(styled));
740 Ok(())
741 }
742
743 pub fn hide_layer(&mut self, layer: m::PresentationLayerAssignmentId) {
747 self.hidden
748 .push(m::InvisibleItemRef::PresentationLayerAssignment(layer));
749 }
750
751 fn placement(&mut self, f: &Frame) -> Result<m::Axis2Placement3dId, AuthorError> {
753 let a = &mut self.author;
754 let location = a.add_cartesian_point(String::new(), f.origin.to_vec())?;
755 let axis = a.add_direction(String::new(), f.axis.to_vec())?;
756 let ref_direction = a.add_direction(String::new(), f.ref_dir.to_vec())?;
757 a.add_axis2_placement3d(
758 String::new(),
759 m::CartesianPointRef::CartesianPoint(location),
760 Some(m::DirectionRef::Direction(axis)),
761 Some(m::DirectionRef::Direction(ref_direction)),
762 )
763 }
764
765 fn control_points(
767 &mut self,
768 row: &[[f64; 3]],
769 ) -> Result<Vec<m::CartesianPointRef>, AuthorError> {
770 row.iter()
771 .map(|p| {
772 self.author
773 .add_cartesian_point(String::new(), p.to_vec())
774 .map(m::CartesianPointRef::CartesianPoint)
775 })
776 .collect()
777 }
778
779 fn profile_geometry(&mut self, profile: &ProfileInput) -> Result<m::CurveRef, AuthorError> {
781 match profile {
782 ProfileInput::Line(point, dir) => {
783 let a = &mut self.author;
784 let pnt = a.add_cartesian_point(String::new(), point.to_vec())?;
785 let orientation = a.add_direction(String::new(), dir.to_vec())?;
786 let vector =
787 a.add_vector(String::new(), m::DirectionRef::Direction(orientation), 1.0)?;
788 let line = a.add_line(
789 String::new(),
790 m::CartesianPointRef::CartesianPoint(pnt),
791 m::VectorRef::Vector(vector),
792 )?;
793 Ok(m::CurveRef::Line(line))
794 }
795 ProfileInput::Circle(frame, radius) => {
796 let position = self.placement(frame)?;
797 let circle = self.author.add_circle(
798 String::new(),
799 m::Axis2PlacementRef::Axis2Placement3d(position),
800 *radius,
801 )?;
802 Ok(m::CurveRef::Circle(circle))
803 }
804 ProfileInput::Ellipse(frame, semi_1, semi_2) => {
805 let position = self.placement(frame)?;
806 let ellipse = self.author.add_ellipse(
807 String::new(),
808 m::Axis2PlacementRef::Axis2Placement3d(position),
809 *semi_1,
810 *semi_2,
811 )?;
812 Ok(m::CurveRef::Ellipse(ellipse))
813 }
814 ProfileInput::Nurbs(curve) => self.nurbs_curve_geometry(curve),
815 }
816 }
817
818 #[allow(clippy::float_cmp)]
824 fn nurbs_curve_geometry(&mut self, c: &NurbsCurve) -> Result<m::CurveRef, AuthorError> {
825 let cps = self.control_points(&c.control_points)?;
826 let (knots, knot_multiplicities) = compress_knots(&c.knots);
827 let degree = i64::try_from(c.degree).unwrap_or(i64::MAX);
828 if c.weights.iter().all(|w| *w == 1.0) {
829 let id = self.author.add_b_spline_curve_with_knots(
830 String::new(),
831 degree,
832 cps,
833 m::BSplineCurveForm::Unspecified,
834 m::Logical::Unknown,
835 m::Logical::Unknown,
836 knot_multiplicities,
837 knots,
838 m::KnotType::Unspecified,
839 )?;
840 Ok(m::CurveRef::BSplineCurveWithKnots(id))
841 } else {
842 let id = self.author.add_complex(vec![
843 m::UnitPart::BoundedCurve,
844 m::UnitPart::BSplineCurve {
845 degree,
846 control_points_list: cps,
847 curve_form: m::BSplineCurveForm::Unspecified,
848 closed_curve: m::Logical::Unknown,
849 self_intersect: m::Logical::Unknown,
850 },
851 m::UnitPart::BSplineCurveWithKnots {
852 knot_multiplicities,
853 knots,
854 knot_spec: m::KnotType::Unspecified,
855 },
856 m::UnitPart::Curve,
857 m::UnitPart::GeometricRepresentationItem,
858 m::UnitPart::RationalBSplineCurve {
859 weights_data: c.weights.clone(),
860 },
861 m::UnitPart::RepresentationItem {
862 name: String::new(),
863 },
864 ])?;
865 Ok(m::CurveRef::Complex(id))
866 }
867 }
868
869 #[allow(clippy::float_cmp)] fn nurbs_surface_geometry(&mut self, s: &NurbsSurface) -> Result<m::SurfaceRef, AuthorError> {
872 let mut cps = Vec::with_capacity(s.control_points.len());
873 for row in &s.control_points {
874 cps.push(self.control_points(row)?);
875 }
876 let (u_knots, u_multiplicities) = compress_knots(&s.knots_u);
877 let (v_knots, v_multiplicities) = compress_knots(&s.knots_v);
878 let u_degree = i64::try_from(s.degree_u).unwrap_or(i64::MAX);
879 let v_degree = i64::try_from(s.degree_v).unwrap_or(i64::MAX);
880 if s.weights.iter().flatten().all(|w| *w == 1.0) {
881 let id = self.author.add_b_spline_surface_with_knots(
882 String::new(),
883 u_degree,
884 v_degree,
885 cps,
886 m::BSplineSurfaceForm::Unspecified,
887 m::Logical::Unknown,
888 m::Logical::Unknown,
889 m::Logical::Unknown,
890 u_multiplicities,
891 v_multiplicities,
892 u_knots,
893 v_knots,
894 m::KnotType::Unspecified,
895 )?;
896 Ok(m::SurfaceRef::BSplineSurfaceWithKnots(id))
897 } else {
898 let id = self.author.add_complex(vec![
899 m::UnitPart::BoundedSurface,
900 m::UnitPart::BSplineSurface {
901 u_degree,
902 v_degree,
903 control_points_list: cps,
904 surface_form: m::BSplineSurfaceForm::Unspecified,
905 u_closed: m::Logical::Unknown,
906 v_closed: m::Logical::Unknown,
907 self_intersect: m::Logical::Unknown,
908 },
909 m::UnitPart::BSplineSurfaceWithKnots {
910 u_multiplicities,
911 v_multiplicities,
912 u_knots,
913 v_knots,
914 knot_spec: m::KnotType::Unspecified,
915 },
916 m::UnitPart::GeometricRepresentationItem,
917 m::UnitPart::RationalBSplineSurface {
918 weights_data: s.weights.clone(),
919 },
920 m::UnitPart::RepresentationItem {
921 name: String::new(),
922 },
923 m::UnitPart::Surface,
924 ])?;
925 Ok(m::SurfaceRef::Complex(id))
926 }
927 }
928
929 pub fn vertex(&mut self, p: [f64; 3]) -> Result<Vertex, AuthorError> {
935 let point = self.author.add_cartesian_point(String::new(), p.to_vec())?;
936 let vp = self
937 .author
938 .add_vertex_point(String::new(), m::PointRef::CartesianPoint(point))?;
939 self.vertices.push((vp, p));
940 Ok(Vertex(self.vertices.len() - 1))
941 }
942
943 pub fn edge(
953 &mut self,
954 from: Vertex,
955 to: Vertex,
956 curve: CurveInput,
957 ) -> Result<m::EdgeCurveId, AuthorError> {
958 let (start_vertex, start) = self.vertices[from.0];
959 let (end_vertex, end) = self.vertices[to.0];
960 let geometry = match curve {
961 CurveInput::Line => {
962 let delta = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
963 let len = (delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]).sqrt();
964 let dir = if len < 1e-12 {
965 [0.0, 0.0, 1.0]
966 } else {
967 [delta[0] / len, delta[1] / len, delta[2] / len]
968 };
969 let a = &mut self.author;
970 let pnt = a.add_cartesian_point(String::new(), start.to_vec())?;
971 let orientation = a.add_direction(String::new(), dir.to_vec())?;
972 let vector =
973 a.add_vector(String::new(), m::DirectionRef::Direction(orientation), len)?;
974 let line = a.add_line(
975 String::new(),
976 m::CartesianPointRef::CartesianPoint(pnt),
977 m::VectorRef::Vector(vector),
978 )?;
979 m::CurveRef::Line(line)
980 }
981 CurveInput::Circle(frame, radius) => {
982 let position = self.placement(&frame)?;
983 let circle = self.author.add_circle(
984 String::new(),
985 m::Axis2PlacementRef::Axis2Placement3d(position),
986 radius,
987 )?;
988 m::CurveRef::Circle(circle)
989 }
990 CurveInput::Ellipse(frame, semi_1, semi_2) => {
991 self.profile_geometry(&ProfileInput::Ellipse(frame, semi_1, semi_2))?
992 }
993 CurveInput::Polyline(points) => {
994 let refs = self.control_points(&points)?;
995 m::CurveRef::Polyline(self.author.add_polyline(String::new(), refs)?)
996 }
997 CurveInput::Nurbs(curve) => self.nurbs_curve_geometry(&curve)?,
998 };
999 self.author.add_edge_curve(
1000 String::new(),
1001 m::VertexRef::VertexPoint(start_vertex),
1002 m::VertexRef::VertexPoint(end_vertex),
1003 geometry,
1004 true,
1005 )
1006 }
1007
1008 fn surface_geometry(&mut self, surface: SurfaceInput) -> Result<m::SurfaceRef, AuthorError> {
1010 Ok(match surface {
1011 SurfaceInput::Plane(frame) => {
1012 let position = self.placement(&frame)?;
1013 let plane = self.author.add_plane(
1014 String::new(),
1015 m::Axis2Placement3dRef::Axis2Placement3d(position),
1016 )?;
1017 m::SurfaceRef::Plane(plane)
1018 }
1019 SurfaceInput::Cylinder(frame, radius) => {
1020 let position = self.placement(&frame)?;
1021 let cyl = self.author.add_cylindrical_surface(
1022 String::new(),
1023 m::Axis2Placement3dRef::Axis2Placement3d(position),
1024 radius,
1025 )?;
1026 m::SurfaceRef::CylindricalSurface(cyl)
1027 }
1028 SurfaceInput::Sphere(frame, radius) => {
1029 let position = self.placement(&frame)?;
1030 let sphere = self.author.add_spherical_surface(
1031 String::new(),
1032 m::Axis2Placement3dRef::Axis2Placement3d(position),
1033 radius,
1034 )?;
1035 m::SurfaceRef::SphericalSurface(sphere)
1036 }
1037 SurfaceInput::Torus(frame, major_radius, minor_radius) => {
1038 let position = self.placement(&frame)?;
1039 let torus = self.author.add_toroidal_surface(
1040 String::new(),
1041 m::Axis2Placement3dRef::Axis2Placement3d(position),
1042 major_radius,
1043 minor_radius,
1044 )?;
1045 m::SurfaceRef::ToroidalSurface(torus)
1046 }
1047 SurfaceInput::Cone(frame, radius, semi_angle) => {
1048 let position = self.placement(&frame)?;
1049 let cone = self.author.add_conical_surface(
1050 String::new(),
1051 m::Axis2Placement3dRef::Axis2Placement3d(position),
1052 radius,
1053 semi_angle,
1054 )?;
1055 m::SurfaceRef::ConicalSurface(cone)
1056 }
1057 SurfaceInput::LinearExtrusion(profile, sweep) => {
1058 let swept_curve = self.profile_geometry(&profile)?;
1059 let len = (sweep[0] * sweep[0] + sweep[1] * sweep[1] + sweep[2] * sweep[2]).sqrt();
1060 let dir = if len < 1e-12 {
1061 [0.0, 0.0, 1.0]
1062 } else {
1063 [sweep[0] / len, sweep[1] / len, sweep[2] / len]
1064 };
1065 let a = &mut self.author;
1066 let orientation = a.add_direction(String::new(), dir.to_vec())?;
1067 let vector =
1068 a.add_vector(String::new(), m::DirectionRef::Direction(orientation), len)?;
1069 let surf = a.add_surface_of_linear_extrusion(
1070 String::new(),
1071 swept_curve,
1072 m::VectorRef::Vector(vector),
1073 )?;
1074 m::SurfaceRef::SurfaceOfLinearExtrusion(surf)
1075 }
1076 SurfaceInput::Revolution(profile, origin, axis) => {
1077 let swept_curve = self.profile_geometry(&profile)?;
1078 let a = &mut self.author;
1079 let location = a.add_cartesian_point(String::new(), origin.to_vec())?;
1080 let axis_dir = a.add_direction(String::new(), axis.to_vec())?;
1081 let position = a.add_axis1_placement(
1082 String::new(),
1083 m::CartesianPointRef::CartesianPoint(location),
1084 Some(m::DirectionRef::Direction(axis_dir)),
1085 )?;
1086 let surf = a.add_surface_of_revolution(
1087 String::new(),
1088 swept_curve,
1089 m::Axis1PlacementRef::Axis1Placement(position),
1090 )?;
1091 m::SurfaceRef::SurfaceOfRevolution(surf)
1092 }
1093 SurfaceInput::Nurbs(surface) => self.nurbs_surface_geometry(&surface)?,
1094 })
1095 }
1096
1097 pub fn face(
1104 &mut self,
1105 surface: SurfaceInput,
1106 same_sense: bool,
1107 bounds: Vec<FaceBoundInput>,
1108 ) -> Result<m::AdvancedFaceId, AuthorError> {
1109 let face_geometry = self.surface_geometry(surface)?;
1110 let mut face_bounds = Vec::with_capacity(bounds.len());
1111 for bound in bounds {
1112 let mut edge_list = Vec::with_capacity(bound.edges.len());
1113 for (edge, forward) in bound.edges {
1114 let oe = self.author.add_oriented_edge(
1115 String::new(),
1116 m::EdgeRef::EdgeCurve(edge),
1117 forward,
1118 )?;
1119 edge_list.push(m::OrientedEdgeRef::OrientedEdge(oe));
1120 }
1121 let el = self.author.add_edge_loop(String::new(), edge_list)?;
1122 let fb = if bound.outer {
1123 m::FaceBoundRef::FaceOuterBound(self.author.add_face_outer_bound(
1124 String::new(),
1125 m::LoopRef::EdgeLoop(el),
1126 bound.orientation,
1127 )?)
1128 } else {
1129 m::FaceBoundRef::FaceBound(self.author.add_face_bound(
1130 String::new(),
1131 m::LoopRef::EdgeLoop(el),
1132 bound.orientation,
1133 )?)
1134 };
1135 face_bounds.push(fb);
1136 }
1137 self.author
1138 .add_advanced_face(String::new(), face_bounds, face_geometry, same_sense)
1139 }
1140
1141 pub fn solid(
1150 &mut self,
1151 part: Part,
1152 name: &str,
1153 faces: Vec<m::AdvancedFaceId>,
1154 ) -> Result<m::ManifoldSolidBrepId, AuthorError> {
1155 let shell = self.author.add_closed_shell(
1156 String::new(),
1157 faces.into_iter().map(m::FaceRef::AdvancedFace).collect(),
1158 )?;
1159 let solid = self
1160 .author
1161 .add_manifold_solid_brep(name.to_owned(), m::ClosedShellRef::ClosedShell(shell))?;
1162 self.parts[part.0]
1163 .items
1164 .push(m::RepresentationItemRef::ManifoldSolidBrep(solid));
1165 Ok(solid)
1166 }
1167
1168 pub fn solid_with_voids(
1186 &mut self,
1187 part: Part,
1188 name: &str,
1189 outer_faces: Vec<m::AdvancedFaceId>,
1190 voids: Vec<Vec<m::AdvancedFaceId>>,
1191 ) -> Result<m::BrepWithVoidsId, AuthorError> {
1192 let outer = self.author.add_closed_shell(
1193 String::new(),
1194 outer_faces
1195 .into_iter()
1196 .map(m::FaceRef::AdvancedFace)
1197 .collect(),
1198 )?;
1199 let mut void_refs = Vec::with_capacity(voids.len());
1200 for void_faces in voids {
1201 let shell = self.author.add_closed_shell(
1202 String::new(),
1203 void_faces
1204 .into_iter()
1205 .map(m::FaceRef::AdvancedFace)
1206 .collect(),
1207 )?;
1208 let oriented = self.author.add_oriented_closed_shell(
1211 String::new(),
1212 m::ClosedShellRef::ClosedShell(shell),
1213 false,
1214 )?;
1215 void_refs.push(m::OrientedClosedShellRef::OrientedClosedShell(oriented));
1216 }
1217 let solid = self.author.add_brep_with_voids(
1218 name.to_owned(),
1219 m::ClosedShellRef::ClosedShell(outer),
1220 void_refs,
1221 )?;
1222 self.parts[part.0]
1223 .items
1224 .push(m::RepresentationItemRef::BrepWithVoids(solid));
1225 Ok(solid)
1226 }
1227
1228 pub fn part(&mut self, name: &str) -> Result<Part, AuthorError> {
1237 self.part_with(name, &PartOptions::default())
1238 }
1239
1240 pub fn part_with(&mut self, name: &str, opts: &PartOptions) -> Result<Part, AuthorError> {
1247 let origin = self.placement(&Frame {
1248 origin: [0.0; 3],
1249 axis: [0.0, 0.0, 1.0],
1250 ref_dir: [1.0, 0.0, 0.0],
1251 })?;
1252 let a = &mut self.author;
1253 let product = a.add_product(
1254 opts.id.clone().unwrap_or_else(|| name.to_owned()),
1255 name.to_owned(),
1256 opts.description.clone(),
1257 vec![m::ProductContextRef::ProductContext(self.product_context)],
1258 )?;
1259 let formation = a.add_product_definition_formation(
1260 opts.version.clone().unwrap_or_default(),
1261 opts.version_description.clone(),
1262 m::ProductRef::Product(product),
1263 )?;
1264 let definition = a.add_product_definition(
1265 "design".to_owned(),
1266 opts.definition_description.clone(),
1267 m::ProductDefinitionFormationRef::ProductDefinitionFormation(formation),
1268 m::ProductDefinitionContextRef::ProductDefinitionContext(self.pd_context),
1269 )?;
1270 let shape = a.add_product_definition_shape(
1271 String::new(),
1272 None,
1273 m::CharacterizedDefinitionRef::ProductDefinition(definition),
1274 )?;
1275
1276 self.parts.push(PendingPart {
1277 product,
1278 formation,
1279 definition,
1280 shape,
1281 origin,
1282 items: Vec::new(),
1283 meshes: Vec::new(),
1284 });
1285 Ok(Part(self.parts.len() - 1))
1286 }
1287
1288 pub fn mesh(
1299 &mut self,
1300 part: Part,
1301 name: &str,
1302 input: &MeshInput,
1303 of_solid: Option<SolidRef>,
1304 ) -> Result<m::TessellatedSolidId, AuthorError> {
1305 let a = &mut self.author;
1306 let npoints = i64::try_from(input.points.len()).unwrap_or(i64::MAX);
1307 let coordinates = a.add_coordinates_list(
1308 String::new(),
1309 npoints,
1310 input.points.iter().map(|p| p.to_vec()).collect(),
1311 )?;
1312 let normals: Vec<Vec<f64>> = match &input.normals {
1313 MeshNormalsInput::None => Vec::new(),
1314 MeshNormalsInput::Uniform(n) => vec![n.to_vec()],
1315 MeshNormalsInput::PerVertex(rows) => rows.iter().map(|n| n.to_vec()).collect(),
1316 };
1317 let to_ix = |v: usize| i64::try_from(v + 1).unwrap_or(i64::MAX);
1321 let triangle_strips: Vec<Vec<i64>> = input
1322 .triangles
1323 .iter()
1324 .map(|&[p, q, r]| vec![to_ix(p), to_ix(r), to_ix(q)])
1325 .collect();
1326 let face = a.add_complex_triangulated_face(
1327 name.to_owned(),
1328 m::CoordinatesListRef::CoordinatesList(coordinates),
1329 npoints,
1330 normals,
1331 None,
1332 Vec::new(),
1333 triangle_strips,
1334 Vec::new(),
1335 )?;
1336 let solid = a.add_tessellated_solid(
1337 name.to_owned(),
1338 vec![m::TessellatedStructuredItemRef::ComplexTriangulatedFace(
1339 face,
1340 )],
1341 of_solid.map(|s| match s {
1342 SolidRef::Solid(id) => m::ManifoldSolidBrepRef::ManifoldSolidBrep(id),
1343 SolidRef::VoidSolid(id) => m::ManifoldSolidBrepRef::BrepWithVoids(id),
1344 }),
1345 )?;
1346 self.parts[part.0].meshes.push(solid);
1347 Ok(solid)
1348 }
1349
1350 pub fn person_and_org(
1357 &mut self,
1358 p: &PersonOrg,
1359 ) -> Result<m::PersonAndOrganizationId, AuthorError> {
1360 let a = &mut self.author;
1361 let person = a.add_person(
1362 p.person_id.clone(),
1363 p.last_name.clone(),
1364 p.first_name.clone(),
1365 None,
1366 None,
1367 None,
1368 )?;
1369 let organization = a.add_organization(None, p.organization.clone(), None)?;
1370 a.add_person_and_organization(
1371 m::PersonRef::Person(person),
1372 m::OrganizationRef::Organization(organization),
1373 )
1374 }
1375
1376 pub fn contributor(
1384 &mut self,
1385 part: Part,
1386 role: &str,
1387 who: m::PersonAndOrganizationId,
1388 ) -> Result<(), AuthorError> {
1389 let a = &mut self.author;
1390 let role = a.add_person_and_organization_role(role.to_owned())?;
1391 a.add_applied_person_and_organization_assignment(
1392 m::PersonAndOrganizationRef::PersonAndOrganization(who),
1393 m::PersonAndOrganizationRoleRef::PersonAndOrganizationRole(role),
1394 vec![m::PersonAndOrganizationItemRef::ProductDefinitionFormation(
1395 self.parts[part.0].formation,
1396 )],
1397 )?;
1398 Ok(())
1399 }
1400
1401 pub fn approve(
1409 &mut self,
1410 part: Part,
1411 input: &ApprovalInput,
1412 ) -> Result<m::ApprovalId, AuthorError> {
1413 let a = &mut self.author;
1414 let status = a.add_approval_status(input.status.clone())?;
1415 let approval = a.add_approval(
1416 m::ApprovalStatusRef::ApprovalStatus(status),
1417 input.level.clone(),
1418 )?;
1419 a.add_applied_approval_assignment(
1420 m::ApprovalRef::Approval(approval),
1421 vec![m::ApprovalItemRef::ProductDefinitionFormation(
1422 self.parts[part.0].formation,
1423 )],
1424 )?;
1425 for (who, role) in &input.approvers {
1426 let role = a.add_approval_role(role.clone())?;
1427 a.add_approval_person_organization(
1428 m::PersonOrganizationSelectRef::PersonAndOrganization(*who),
1429 m::ApprovalRef::Approval(approval),
1430 m::ApprovalRoleRef::ApprovalRole(role),
1431 )?;
1432 }
1433 if let Some(date) = input.date {
1434 let calendar = a.add_calendar_date(date.year, date.day, date.month)?;
1435 let zone = a.add_coordinated_universal_time_offset(0, None, m::AheadOrBehind::Exact)?;
1436 let time = a.add_local_time(
1437 date.hour,
1438 date.minute,
1439 None,
1440 m::CoordinatedUniversalTimeOffsetRef::CoordinatedUniversalTimeOffset(zone),
1441 )?;
1442 let date_time = a.add_date_and_time(
1443 m::DateRef::CalendarDate(calendar),
1444 m::LocalTimeRef::LocalTime(time),
1445 )?;
1446 a.add_approval_date_time(
1447 m::DateTimeSelectRef::DateAndTime(date_time),
1448 m::ApprovalRef::Approval(approval),
1449 )?;
1450 }
1451 Ok(approval)
1452 }
1453
1454 pub fn document(&mut self, part: Part, d: &DocumentInput) -> Result<(), AuthorError> {
1460 let a = &mut self.author;
1461 let kind = a.add_document_type(d.kind.clone())?;
1462 let doc = a.add_document(
1463 d.id.clone(),
1464 d.name.clone(),
1465 d.description.clone(),
1466 m::DocumentTypeRef::DocumentType(kind),
1467 )?;
1468 a.add_applied_document_reference(
1469 m::DocumentRef::Document(doc),
1470 String::new(),
1471 vec![m::DocumentReferenceItemRef::ProductDefinitionFormation(
1472 self.parts[part.0].formation,
1473 )],
1474 )?;
1475 Ok(())
1476 }
1477
1478 pub fn security(
1484 &mut self,
1485 part: Part,
1486 name: &str,
1487 purpose: &str,
1488 level: &str,
1489 ) -> Result<(), AuthorError> {
1490 let a = &mut self.author;
1491 let level = a.add_security_classification_level(level.to_owned())?;
1492 let classification = a.add_security_classification(
1493 name.to_owned(),
1494 purpose.to_owned(),
1495 m::SecurityClassificationLevelRef::SecurityClassificationLevel(level),
1496 )?;
1497 a.add_applied_security_classification_assignment(
1498 m::SecurityClassificationRef::SecurityClassification(classification),
1499 vec![
1500 m::SecurityClassificationItemRef::ProductDefinitionFormation(
1501 self.parts[part.0].formation,
1502 ),
1503 ],
1504 )?;
1505 Ok(())
1506 }
1507
1508 pub fn place(
1517 &mut self,
1518 parent: Part,
1519 child: Part,
1520 at: Frame,
1521 ) -> Result<m::NextAssemblyUsageOccurrenceId, AuthorError> {
1522 let nauo = self.author.add_next_assembly_usage_occurrence(
1523 format!("{}", self.placements.len() + 1),
1524 String::new(),
1525 None,
1526 m::ProductDefinitionOrReferenceRef::ProductDefinition(self.parts[parent.0].definition),
1527 m::ProductDefinitionOrReferenceRef::ProductDefinition(self.parts[child.0].definition),
1528 None,
1529 )?;
1530 let to = self.placement(&at)?;
1531 self.placements.push(PendingPlacement {
1532 nauo,
1533 parent: parent.0,
1534 child: child.0,
1535 to,
1536 });
1537 Ok(nauo)
1538 }
1539
1540 pub fn finish(mut self) -> Result<String, AuthorError> {
1548 let a = &mut self.author;
1549 let mut part_reps: Vec<m::RepresentationOrRepresentationReferenceRef> =
1553 Vec::with_capacity(self.parts.len());
1554 for part in &self.parts {
1555 let mut items = vec![m::RepresentationItemRef::Axis2Placement3d(part.origin)];
1556 items.extend(part.items.iter().cloned());
1557 let has_solid = part.items.iter().any(|i| {
1558 matches!(
1559 i,
1560 m::RepresentationItemRef::ManifoldSolidBrep(_)
1561 | m::RepresentationItemRef::BrepWithVoids(_)
1562 )
1563 });
1564 let (rep, rep_or_ref) = if has_solid {
1565 let id = a.add_advanced_brep_shape_representation(
1566 String::new(),
1567 items,
1568 m::RepresentationContextRef::Complex(self.ctx),
1569 )?;
1570 (
1571 m::RepresentationRef::AdvancedBrepShapeRepresentation(id),
1572 m::RepresentationOrRepresentationReferenceRef::AdvancedBrepShapeRepresentation(
1573 id,
1574 ),
1575 )
1576 } else {
1577 let id = a.add_shape_representation(
1578 String::new(),
1579 items,
1580 m::RepresentationContextRef::Complex(self.ctx),
1581 )?;
1582 (
1583 m::RepresentationRef::ShapeRepresentation(id),
1584 m::RepresentationOrRepresentationReferenceRef::ShapeRepresentation(id),
1585 )
1586 };
1587 a.add_shape_definition_representation(
1588 m::RepresentedDefinitionRef::ProductDefinitionShape(part.shape),
1589 rep,
1590 )?;
1591 part_reps.push(rep_or_ref);
1592
1593 if !part.meshes.is_empty() {
1596 let tsr = a.add_tessellated_shape_representation(
1597 String::new(),
1598 part.meshes
1599 .iter()
1600 .map(|&t| m::RepresentationItemRef::TessellatedSolid(t))
1601 .collect(),
1602 m::RepresentationContextRef::Complex(self.ctx),
1603 )?;
1604 a.add_shape_definition_representation(
1605 m::RepresentedDefinitionRef::ProductDefinitionShape(part.shape),
1606 m::RepresentationRef::TessellatedShapeRepresentation(tsr),
1607 )?;
1608 }
1609 }
1610 bind_placements(a, &self.parts, &self.placements, &part_reps)?;
1612 if !self.parts.is_empty() {
1613 a.add_product_related_product_category(
1614 "part".to_owned(),
1615 None,
1616 self.parts
1617 .iter()
1618 .map(|p| m::ProductRef::Product(p.product))
1619 .collect(),
1620 )?;
1621 }
1622 if !self.hidden.is_empty() {
1625 a.add_invisibility(std::mem::take(&mut self.hidden))?;
1626 }
1627 if !self.styles.is_empty() {
1629 a.add_mechanical_design_geometric_presentation_representation(
1630 String::new(),
1631 self.styles
1632 .iter()
1633 .map(|&s| m::RepresentationItemRef::StyledItem(s))
1634 .collect(),
1635 m::RepresentationContextRef::Complex(self.ctx),
1636 )?;
1637 }
1638 let h = &self.header;
1639 let file_header = FileHeader {
1640 description: h.description.clone().unwrap_or_default(),
1641 file_name: h.file_name.clone().unwrap_or_default(),
1642 time_stamp: h.timestamp.clone().unwrap_or_else(now_iso_utc),
1643 authors: h.authors.clone(),
1644 organizations: h.organizations.clone(),
1645 preprocessor_version: concat!("step-io ", env!("CARGO_PKG_VERSION")).to_owned(),
1646 originating_system: h.originating_system.clone().unwrap_or_default(),
1647 authorisation: h.authorisation.clone().unwrap_or_default(),
1648 schema: crate::parser::SchemaId::default(),
1651 };
1652 Ok(self.author.finish_with_header(&file_header))
1653 }
1654}
1655
1656fn bind_placements(
1662 a: &mut Ap242Author,
1663 parts: &[PendingPart],
1664 placements: &[PendingPlacement],
1665 part_reps: &[m::RepresentationOrRepresentationReferenceRef],
1666) -> Result<(), AuthorError> {
1667 for placement in placements {
1668 let pds = a.add_product_definition_shape(
1669 "Placement".to_owned(),
1670 Some("Placement of an item".to_owned()),
1671 m::CharacterizedDefinitionRef::NextAssemblyUsageOccurrence(placement.nauo),
1672 )?;
1673 let idt = a.add_item_defined_transformation(
1674 String::new(),
1675 None,
1676 m::RepresentationItemRef::Axis2Placement3d(parts[placement.parent].origin),
1677 m::RepresentationItemRef::Axis2Placement3d(placement.to),
1678 )?;
1679 let rrwt = a.add_complex(vec![
1680 m::UnitPart::RepresentationRelationship {
1681 name: String::new(),
1682 description: None,
1683 rep_1: part_reps[placement.child].clone(),
1684 rep_2: part_reps[placement.parent].clone(),
1685 },
1686 m::UnitPart::RepresentationRelationshipWithTransformation {
1687 transformation_operator: m::TransformationRef::ItemDefinedTransformation(idt),
1688 },
1689 m::UnitPart::ShapeRepresentationRelationship,
1690 ])?;
1691 a.add_context_dependent_shape_representation(
1692 m::ShapeRepresentationRelationshipRef::Complex(rrwt),
1693 m::ProductDefinitionShapeRef::ProductDefinitionShape(pds),
1694 )?;
1695 }
1696 Ok(())
1697}
1698
1699#[cfg(test)]
1700mod tests {
1701 use super::iso_utc_from_unix;
1702
1703 #[test]
1704 fn iso_utc_from_unix_known_values() {
1705 assert_eq!(iso_utc_from_unix(0), "1970-01-01T00:00:00Z");
1706 assert_eq!(iso_utc_from_unix(1_709_251_199), "2024-02-29T23:59:59Z");
1708 assert_eq!(iso_utc_from_unix(1_709_251_200), "2024-03-01T00:00:00Z");
1710 }
1711}