1use crate::emit::FileHeader;
133use crate::generated::author::{Ap242Author, AuthorError};
134use crate::generated::model as m;
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}
413
414#[derive(Debug, Copy, Clone, PartialEq, Eq)]
419pub struct Vertex(usize);
420
421#[derive(Debug, Copy, Clone, PartialEq, Eq)]
425pub struct Part(usize);
426
427struct PendingPart {
431 product: m::ProductId,
432 formation: m::ProductDefinitionFormationId,
433 definition: m::ProductDefinitionId,
434 shape: m::ProductDefinitionShapeId,
435 origin: m::Axis2Placement3dId,
436 items: Vec<m::RepresentationItemRef>,
437 meshes: Vec<m::TessellatedSolidId>,
438}
439
440struct PendingPlacement {
445 nauo: m::NextAssemblyUsageOccurrenceId,
446 parent: usize,
447 child: usize,
448 to: m::Axis2Placement3dId,
449}
450
451pub struct StepBuilder {
455 author: Ap242Author,
456 ctx: m::ComplexUnitId,
457 product_context: m::ProductContextId,
458 pd_context: m::ProductDefinitionContextId,
459 parts: Vec<PendingPart>,
460 placements: Vec<PendingPlacement>,
461 vertices: Vec<(m::VertexPointId, [f64; 3])>,
462 styles: Vec<m::StyledItemId>,
463 hidden: Vec<m::InvisibleItemRef>,
464 header: HeaderInput,
465}
466
467impl StepBuilder {
468 pub fn new() -> Result<Self, AuthorError> {
475 Self::new_with(&UnitsInput::default())
476 }
477
478 pub fn new_with(units: &UnitsInput) -> Result<Self, AuthorError> {
487 let mut a = Ap242Author::new();
488
489 let ac = a.add_application_context("managed model based 3d engineering".to_owned())?;
490 a.add_application_protocol_definition(
491 "international standard".to_owned(),
492 "ap242_managed_model_based_3d_engineering_mim_lf".to_owned(),
493 2011,
494 m::ApplicationContextRef::ApplicationContext(ac),
495 )?;
496 let product_context = a.add_product_context(
497 String::new(),
498 m::ApplicationContextRef::ApplicationContext(ac),
499 "mechanical".to_owned(),
500 )?;
501 let pd_context = a.add_product_definition_context(
502 "part definition".to_owned(),
503 m::ApplicationContextRef::ApplicationContext(ac),
504 "design".to_owned(),
505 )?;
506
507 let length_prefix = match units.length {
508 LengthUnit::Millimetre => Some(m::SiPrefix::Milli),
509 LengthUnit::Metre => None,
510 };
511 let mm = a.add_complex(vec![
512 m::UnitPart::LengthUnit,
513 m::UnitPart::NamedUnit { dimensions: None },
514 m::UnitPart::SiUnit {
515 prefix: length_prefix,
516 name: m::SiUnitName::Metre,
517 },
518 ])?;
519 let rad = a.add_complex(vec![
520 m::UnitPart::NamedUnit { dimensions: None },
521 m::UnitPart::PlaneAngleUnit,
522 m::UnitPart::SiUnit {
523 prefix: None,
524 name: m::SiUnitName::Radian,
525 },
526 ])?;
527 let sr = a.add_complex(vec![
528 m::UnitPart::NamedUnit { dimensions: None },
529 m::UnitPart::SiUnit {
530 prefix: None,
531 name: m::SiUnitName::Steradian,
532 },
533 m::UnitPart::SolidAngleUnit,
534 ])?;
535 let uncertainty = a.add_uncertainty_measure_with_unit(
536 m::MeasureValue {
537 type_name: Some("LENGTH_MEASURE".to_owned()),
538 value: m::MeasureScalar::Real(units.uncertainty),
539 },
540 m::UnitRef::Complex(mm),
541 "distance_accuracy_value".to_owned(),
542 Some("confusion accuracy".to_owned()),
543 )?;
544 let ctx = a.add_complex(vec![
545 m::UnitPart::GeometricRepresentationContext {
546 coordinate_space_dimension: 3,
547 },
548 m::UnitPart::GlobalUncertaintyAssignedContext {
549 uncertainty: vec![
550 m::UncertaintyMeasureWithUnitRef::UncertaintyMeasureWithUnit(uncertainty),
551 ],
552 },
553 m::UnitPart::GlobalUnitAssignedContext {
554 units: vec![
555 m::UnitRef::Complex(mm),
556 m::UnitRef::Complex(rad),
557 m::UnitRef::Complex(sr),
558 ],
559 },
560 m::UnitPart::RepresentationContext {
561 context_identifier: String::new(),
562 context_type: "3D".to_owned(),
563 },
564 ])?;
565
566 Ok(Self {
567 author: a,
568 ctx,
569 product_context,
570 pd_context,
571 parts: Vec::new(),
572 placements: Vec::new(),
573 vertices: Vec::new(),
574 styles: Vec::new(),
575 hidden: Vec::new(),
576 header: HeaderInput::default(),
577 })
578 }
579
580 pub fn header(&mut self, h: &HeaderInput) {
586 self.header = h.clone();
587 }
588
589 pub fn author(&mut self) -> &mut Ap242Author {
600 &mut self.author
601 }
602
603 pub fn style(
612 &mut self,
613 target: StyleTarget,
614 color: Rgb,
615 transparency: Option<f64>,
616 ) -> Result<m::StyledItemId, AuthorError> {
617 let a = &mut self.author;
618 let rgb = a.add_colour_rgb(String::new(), color.red, color.green, color.blue)?;
619 let element = if let Some(t) = transparency {
622 let transparent = a.add_surface_style_transparent(t)?;
623 let rendering = a.add_surface_style_rendering_with_properties(
624 m::ShadingSurfaceMethod::NormalShading,
625 m::ColourRef::ColourRgb(rgb),
626 vec![m::RenderingPropertiesSelectRef::SurfaceStyleTransparent(
627 transparent,
628 )],
629 )?;
630 m::SurfaceStyleElementSelectRef::SurfaceStyleRenderingWithProperties(rendering)
631 } else {
632 let fill_colour =
633 a.add_fill_area_style_colour(String::new(), m::ColourRef::ColourRgb(rgb))?;
634 let fill_style = a.add_fill_area_style(
635 String::new(),
636 vec![m::FillStyleSelectRef::FillAreaStyleColour(fill_colour)],
637 )?;
638 let fill_area =
639 a.add_surface_style_fill_area(m::FillAreaStyleRef::FillAreaStyle(fill_style))?;
640 m::SurfaceStyleElementSelectRef::SurfaceStyleFillArea(fill_area)
641 };
642 let side_style = a.add_surface_side_style(String::new(), vec![element])?;
643 let usage = a.add_surface_style_usage(
644 m::SurfaceSide::Both,
645 m::SurfaceSideStyleSelectRef::SurfaceSideStyle(side_style),
646 )?;
647 let assignment = a.add_presentation_style_assignment(vec![
648 m::PresentationStyleSelectRef::SurfaceStyleUsage(usage),
649 ])?;
650 let item = match target {
651 StyleTarget::Face(f) => m::StyledItemTargetRef::AdvancedFace(f),
652 StyleTarget::Solid(s) => m::StyledItemTargetRef::ManifoldSolidBrep(s),
653 };
654 let styled = a.add_styled_item(
655 String::new(),
656 vec![m::PresentationStyleAssignmentRef::PresentationStyleAssignment(assignment)],
657 item,
658 )?;
659 self.styles.push(styled);
660 Ok(styled)
661 }
662
663 pub fn layer(
672 &mut self,
673 name: &str,
674 targets: Vec<StyleTarget>,
675 ) -> Result<m::PresentationLayerAssignmentId, AuthorError> {
676 let items = targets
677 .into_iter()
678 .map(|t| match t {
679 StyleTarget::Face(f) => m::LayeredItemRef::AdvancedFace(f),
680 StyleTarget::Solid(s) => m::LayeredItemRef::ManifoldSolidBrep(s),
681 })
682 .collect();
683 self.author
684 .add_presentation_layer_assignment(name.to_owned(), String::new(), items)
685 }
686
687 pub fn hide(&mut self, target: StyleTarget) -> Result<(), AuthorError> {
697 let a = &mut self.author;
698 let assignment =
699 a.add_presentation_style_assignment(vec![m::PresentationStyleSelectRef::NullStyle(
700 m::NullStyle::Null,
701 )])?;
702 let item = match target {
703 StyleTarget::Face(f) => m::StyledItemTargetRef::AdvancedFace(f),
704 StyleTarget::Solid(s) => m::StyledItemTargetRef::ManifoldSolidBrep(s),
705 };
706 let styled = a.add_styled_item(
707 String::new(),
708 vec![m::PresentationStyleAssignmentRef::PresentationStyleAssignment(assignment)],
709 item,
710 )?;
711 self.styles.push(styled);
712 self.hidden.push(m::InvisibleItemRef::StyledItem(styled));
713 Ok(())
714 }
715
716 pub fn hide_layer(&mut self, layer: m::PresentationLayerAssignmentId) {
720 self.hidden
721 .push(m::InvisibleItemRef::PresentationLayerAssignment(layer));
722 }
723
724 fn placement(&mut self, f: &Frame) -> Result<m::Axis2Placement3dId, AuthorError> {
726 let a = &mut self.author;
727 let location = a.add_cartesian_point(String::new(), f.origin.to_vec())?;
728 let axis = a.add_direction(String::new(), f.axis.to_vec())?;
729 let ref_direction = a.add_direction(String::new(), f.ref_dir.to_vec())?;
730 a.add_axis2_placement3d(
731 String::new(),
732 m::CartesianPointRef::CartesianPoint(location),
733 Some(m::DirectionRef::Direction(axis)),
734 Some(m::DirectionRef::Direction(ref_direction)),
735 )
736 }
737
738 fn control_points(
740 &mut self,
741 row: &[[f64; 3]],
742 ) -> Result<Vec<m::CartesianPointRef>, AuthorError> {
743 row.iter()
744 .map(|p| {
745 self.author
746 .add_cartesian_point(String::new(), p.to_vec())
747 .map(m::CartesianPointRef::CartesianPoint)
748 })
749 .collect()
750 }
751
752 fn profile_geometry(&mut self, profile: &ProfileInput) -> Result<m::CurveRef, AuthorError> {
754 match profile {
755 ProfileInput::Line(point, dir) => {
756 let a = &mut self.author;
757 let pnt = a.add_cartesian_point(String::new(), point.to_vec())?;
758 let orientation = a.add_direction(String::new(), dir.to_vec())?;
759 let vector =
760 a.add_vector(String::new(), m::DirectionRef::Direction(orientation), 1.0)?;
761 let line = a.add_line(
762 String::new(),
763 m::CartesianPointRef::CartesianPoint(pnt),
764 m::VectorRef::Vector(vector),
765 )?;
766 Ok(m::CurveRef::Line(line))
767 }
768 ProfileInput::Circle(frame, radius) => {
769 let position = self.placement(frame)?;
770 let circle = self.author.add_circle(
771 String::new(),
772 m::Axis2PlacementRef::Axis2Placement3d(position),
773 *radius,
774 )?;
775 Ok(m::CurveRef::Circle(circle))
776 }
777 ProfileInput::Ellipse(frame, semi_1, semi_2) => {
778 let position = self.placement(frame)?;
779 let ellipse = self.author.add_ellipse(
780 String::new(),
781 m::Axis2PlacementRef::Axis2Placement3d(position),
782 *semi_1,
783 *semi_2,
784 )?;
785 Ok(m::CurveRef::Ellipse(ellipse))
786 }
787 ProfileInput::Nurbs(curve) => self.nurbs_curve_geometry(curve),
788 }
789 }
790
791 #[allow(clippy::float_cmp)]
797 fn nurbs_curve_geometry(&mut self, c: &NurbsCurve) -> Result<m::CurveRef, AuthorError> {
798 let cps = self.control_points(&c.control_points)?;
799 let (knots, knot_multiplicities) = compress_knots(&c.knots);
800 let degree = i64::try_from(c.degree).unwrap_or(i64::MAX);
801 if c.weights.iter().all(|w| *w == 1.0) {
802 let id = self.author.add_b_spline_curve_with_knots(
803 String::new(),
804 degree,
805 cps,
806 m::BSplineCurveForm::Unspecified,
807 m::Logical::Unknown,
808 m::Logical::Unknown,
809 knot_multiplicities,
810 knots,
811 m::KnotType::Unspecified,
812 )?;
813 Ok(m::CurveRef::BSplineCurveWithKnots(id))
814 } else {
815 let id = self.author.add_complex(vec![
816 m::UnitPart::BoundedCurve,
817 m::UnitPart::BSplineCurve {
818 degree,
819 control_points_list: cps,
820 curve_form: m::BSplineCurveForm::Unspecified,
821 closed_curve: m::Logical::Unknown,
822 self_intersect: m::Logical::Unknown,
823 },
824 m::UnitPart::BSplineCurveWithKnots {
825 knot_multiplicities,
826 knots,
827 knot_spec: m::KnotType::Unspecified,
828 },
829 m::UnitPart::Curve,
830 m::UnitPart::GeometricRepresentationItem,
831 m::UnitPart::RationalBSplineCurve {
832 weights_data: c.weights.clone(),
833 },
834 m::UnitPart::RepresentationItem {
835 name: String::new(),
836 },
837 ])?;
838 Ok(m::CurveRef::Complex(id))
839 }
840 }
841
842 #[allow(clippy::float_cmp)] fn nurbs_surface_geometry(&mut self, s: &NurbsSurface) -> Result<m::SurfaceRef, AuthorError> {
845 let mut cps = Vec::with_capacity(s.control_points.len());
846 for row in &s.control_points {
847 cps.push(self.control_points(row)?);
848 }
849 let (u_knots, u_multiplicities) = compress_knots(&s.knots_u);
850 let (v_knots, v_multiplicities) = compress_knots(&s.knots_v);
851 let u_degree = i64::try_from(s.degree_u).unwrap_or(i64::MAX);
852 let v_degree = i64::try_from(s.degree_v).unwrap_or(i64::MAX);
853 if s.weights.iter().flatten().all(|w| *w == 1.0) {
854 let id = self.author.add_b_spline_surface_with_knots(
855 String::new(),
856 u_degree,
857 v_degree,
858 cps,
859 m::BSplineSurfaceForm::Unspecified,
860 m::Logical::Unknown,
861 m::Logical::Unknown,
862 m::Logical::Unknown,
863 u_multiplicities,
864 v_multiplicities,
865 u_knots,
866 v_knots,
867 m::KnotType::Unspecified,
868 )?;
869 Ok(m::SurfaceRef::BSplineSurfaceWithKnots(id))
870 } else {
871 let id = self.author.add_complex(vec![
872 m::UnitPart::BoundedSurface,
873 m::UnitPart::BSplineSurface {
874 u_degree,
875 v_degree,
876 control_points_list: cps,
877 surface_form: m::BSplineSurfaceForm::Unspecified,
878 u_closed: m::Logical::Unknown,
879 v_closed: m::Logical::Unknown,
880 self_intersect: m::Logical::Unknown,
881 },
882 m::UnitPart::BSplineSurfaceWithKnots {
883 u_multiplicities,
884 v_multiplicities,
885 u_knots,
886 v_knots,
887 knot_spec: m::KnotType::Unspecified,
888 },
889 m::UnitPart::GeometricRepresentationItem,
890 m::UnitPart::RationalBSplineSurface {
891 weights_data: s.weights.clone(),
892 },
893 m::UnitPart::RepresentationItem {
894 name: String::new(),
895 },
896 m::UnitPart::Surface,
897 ])?;
898 Ok(m::SurfaceRef::Complex(id))
899 }
900 }
901
902 pub fn vertex(&mut self, p: [f64; 3]) -> Result<Vertex, AuthorError> {
908 let point = self.author.add_cartesian_point(String::new(), p.to_vec())?;
909 let vp = self
910 .author
911 .add_vertex_point(String::new(), m::PointRef::CartesianPoint(point))?;
912 self.vertices.push((vp, p));
913 Ok(Vertex(self.vertices.len() - 1))
914 }
915
916 pub fn edge(
926 &mut self,
927 from: Vertex,
928 to: Vertex,
929 curve: CurveInput,
930 ) -> Result<m::EdgeCurveId, AuthorError> {
931 let (start_vertex, start) = self.vertices[from.0];
932 let (end_vertex, end) = self.vertices[to.0];
933 let geometry = match curve {
934 CurveInput::Line => {
935 let delta = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
936 let len = (delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]).sqrt();
937 let dir = if len < 1e-12 {
938 [0.0, 0.0, 1.0]
939 } else {
940 [delta[0] / len, delta[1] / len, delta[2] / len]
941 };
942 let a = &mut self.author;
943 let pnt = a.add_cartesian_point(String::new(), start.to_vec())?;
944 let orientation = a.add_direction(String::new(), dir.to_vec())?;
945 let vector =
946 a.add_vector(String::new(), m::DirectionRef::Direction(orientation), len)?;
947 let line = a.add_line(
948 String::new(),
949 m::CartesianPointRef::CartesianPoint(pnt),
950 m::VectorRef::Vector(vector),
951 )?;
952 m::CurveRef::Line(line)
953 }
954 CurveInput::Circle(frame, radius) => {
955 let position = self.placement(&frame)?;
956 let circle = self.author.add_circle(
957 String::new(),
958 m::Axis2PlacementRef::Axis2Placement3d(position),
959 radius,
960 )?;
961 m::CurveRef::Circle(circle)
962 }
963 CurveInput::Ellipse(frame, semi_1, semi_2) => {
964 self.profile_geometry(&ProfileInput::Ellipse(frame, semi_1, semi_2))?
965 }
966 CurveInput::Polyline(points) => {
967 let refs = self.control_points(&points)?;
968 m::CurveRef::Polyline(self.author.add_polyline(String::new(), refs)?)
969 }
970 CurveInput::Nurbs(curve) => self.nurbs_curve_geometry(&curve)?,
971 };
972 self.author.add_edge_curve(
973 String::new(),
974 m::VertexRef::VertexPoint(start_vertex),
975 m::VertexRef::VertexPoint(end_vertex),
976 geometry,
977 true,
978 )
979 }
980
981 fn surface_geometry(&mut self, surface: SurfaceInput) -> Result<m::SurfaceRef, AuthorError> {
983 Ok(match surface {
984 SurfaceInput::Plane(frame) => {
985 let position = self.placement(&frame)?;
986 let plane = self.author.add_plane(
987 String::new(),
988 m::Axis2Placement3dRef::Axis2Placement3d(position),
989 )?;
990 m::SurfaceRef::Plane(plane)
991 }
992 SurfaceInput::Cylinder(frame, radius) => {
993 let position = self.placement(&frame)?;
994 let cyl = self.author.add_cylindrical_surface(
995 String::new(),
996 m::Axis2Placement3dRef::Axis2Placement3d(position),
997 radius,
998 )?;
999 m::SurfaceRef::CylindricalSurface(cyl)
1000 }
1001 SurfaceInput::Sphere(frame, radius) => {
1002 let position = self.placement(&frame)?;
1003 let sphere = self.author.add_spherical_surface(
1004 String::new(),
1005 m::Axis2Placement3dRef::Axis2Placement3d(position),
1006 radius,
1007 )?;
1008 m::SurfaceRef::SphericalSurface(sphere)
1009 }
1010 SurfaceInput::Torus(frame, major_radius, minor_radius) => {
1011 let position = self.placement(&frame)?;
1012 let torus = self.author.add_toroidal_surface(
1013 String::new(),
1014 m::Axis2Placement3dRef::Axis2Placement3d(position),
1015 major_radius,
1016 minor_radius,
1017 )?;
1018 m::SurfaceRef::ToroidalSurface(torus)
1019 }
1020 SurfaceInput::Cone(frame, radius, semi_angle) => {
1021 let position = self.placement(&frame)?;
1022 let cone = self.author.add_conical_surface(
1023 String::new(),
1024 m::Axis2Placement3dRef::Axis2Placement3d(position),
1025 radius,
1026 semi_angle,
1027 )?;
1028 m::SurfaceRef::ConicalSurface(cone)
1029 }
1030 SurfaceInput::LinearExtrusion(profile, sweep) => {
1031 let swept_curve = self.profile_geometry(&profile)?;
1032 let len = (sweep[0] * sweep[0] + sweep[1] * sweep[1] + sweep[2] * sweep[2]).sqrt();
1033 let dir = if len < 1e-12 {
1034 [0.0, 0.0, 1.0]
1035 } else {
1036 [sweep[0] / len, sweep[1] / len, sweep[2] / len]
1037 };
1038 let a = &mut self.author;
1039 let orientation = a.add_direction(String::new(), dir.to_vec())?;
1040 let vector =
1041 a.add_vector(String::new(), m::DirectionRef::Direction(orientation), len)?;
1042 let surf = a.add_surface_of_linear_extrusion(
1043 String::new(),
1044 swept_curve,
1045 m::VectorRef::Vector(vector),
1046 )?;
1047 m::SurfaceRef::SurfaceOfLinearExtrusion(surf)
1048 }
1049 SurfaceInput::Revolution(profile, origin, axis) => {
1050 let swept_curve = self.profile_geometry(&profile)?;
1051 let a = &mut self.author;
1052 let location = a.add_cartesian_point(String::new(), origin.to_vec())?;
1053 let axis_dir = a.add_direction(String::new(), axis.to_vec())?;
1054 let position = a.add_axis1_placement(
1055 String::new(),
1056 m::CartesianPointRef::CartesianPoint(location),
1057 Some(m::DirectionRef::Direction(axis_dir)),
1058 )?;
1059 let surf = a.add_surface_of_revolution(
1060 String::new(),
1061 swept_curve,
1062 m::Axis1PlacementRef::Axis1Placement(position),
1063 )?;
1064 m::SurfaceRef::SurfaceOfRevolution(surf)
1065 }
1066 SurfaceInput::Nurbs(surface) => self.nurbs_surface_geometry(&surface)?,
1067 })
1068 }
1069
1070 pub fn face(
1077 &mut self,
1078 surface: SurfaceInput,
1079 same_sense: bool,
1080 bounds: Vec<FaceBoundInput>,
1081 ) -> Result<m::AdvancedFaceId, AuthorError> {
1082 let face_geometry = self.surface_geometry(surface)?;
1083 let mut face_bounds = Vec::with_capacity(bounds.len());
1084 for bound in bounds {
1085 let mut edge_list = Vec::with_capacity(bound.edges.len());
1086 for (edge, forward) in bound.edges {
1087 let oe = self.author.add_oriented_edge(
1088 String::new(),
1089 m::EdgeRef::EdgeCurve(edge),
1090 forward,
1091 )?;
1092 edge_list.push(m::OrientedEdgeRef::OrientedEdge(oe));
1093 }
1094 let el = self.author.add_edge_loop(String::new(), edge_list)?;
1095 let fb = if bound.outer {
1096 m::FaceBoundRef::FaceOuterBound(self.author.add_face_outer_bound(
1097 String::new(),
1098 m::LoopRef::EdgeLoop(el),
1099 bound.orientation,
1100 )?)
1101 } else {
1102 m::FaceBoundRef::FaceBound(self.author.add_face_bound(
1103 String::new(),
1104 m::LoopRef::EdgeLoop(el),
1105 bound.orientation,
1106 )?)
1107 };
1108 face_bounds.push(fb);
1109 }
1110 self.author
1111 .add_advanced_face(String::new(), face_bounds, face_geometry, same_sense)
1112 }
1113
1114 pub fn solid(
1123 &mut self,
1124 part: Part,
1125 name: &str,
1126 faces: Vec<m::AdvancedFaceId>,
1127 ) -> Result<m::ManifoldSolidBrepId, AuthorError> {
1128 let shell = self.author.add_closed_shell(
1129 String::new(),
1130 faces.into_iter().map(m::FaceRef::AdvancedFace).collect(),
1131 )?;
1132 let solid = self
1133 .author
1134 .add_manifold_solid_brep(name.to_owned(), m::ClosedShellRef::ClosedShell(shell))?;
1135 self.parts[part.0]
1136 .items
1137 .push(m::RepresentationItemRef::ManifoldSolidBrep(solid));
1138 Ok(solid)
1139 }
1140
1141 pub fn part(&mut self, name: &str) -> Result<Part, AuthorError> {
1150 self.part_with(name, &PartOptions::default())
1151 }
1152
1153 pub fn part_with(&mut self, name: &str, opts: &PartOptions) -> Result<Part, AuthorError> {
1160 let origin = self.placement(&Frame {
1161 origin: [0.0; 3],
1162 axis: [0.0, 0.0, 1.0],
1163 ref_dir: [1.0, 0.0, 0.0],
1164 })?;
1165 let a = &mut self.author;
1166 let product = a.add_product(
1167 opts.id.clone().unwrap_or_else(|| name.to_owned()),
1168 name.to_owned(),
1169 opts.description.clone(),
1170 vec![m::ProductContextRef::ProductContext(self.product_context)],
1171 )?;
1172 let formation = a.add_product_definition_formation(
1173 opts.version.clone().unwrap_or_default(),
1174 opts.version_description.clone(),
1175 m::ProductRef::Product(product),
1176 )?;
1177 let definition = a.add_product_definition(
1178 "design".to_owned(),
1179 opts.definition_description.clone(),
1180 m::ProductDefinitionFormationRef::ProductDefinitionFormation(formation),
1181 m::ProductDefinitionContextRef::ProductDefinitionContext(self.pd_context),
1182 )?;
1183 let shape = a.add_product_definition_shape(
1184 String::new(),
1185 None,
1186 m::CharacterizedDefinitionRef::ProductDefinition(definition),
1187 )?;
1188
1189 self.parts.push(PendingPart {
1190 product,
1191 formation,
1192 definition,
1193 shape,
1194 origin,
1195 items: Vec::new(),
1196 meshes: Vec::new(),
1197 });
1198 Ok(Part(self.parts.len() - 1))
1199 }
1200
1201 pub fn mesh(
1212 &mut self,
1213 part: Part,
1214 name: &str,
1215 input: &MeshInput,
1216 of_solid: Option<m::ManifoldSolidBrepId>,
1217 ) -> Result<m::TessellatedSolidId, AuthorError> {
1218 let a = &mut self.author;
1219 let npoints = i64::try_from(input.points.len()).unwrap_or(i64::MAX);
1220 let coordinates = a.add_coordinates_list(
1221 String::new(),
1222 npoints,
1223 input.points.iter().map(|p| p.to_vec()).collect(),
1224 )?;
1225 let normals: Vec<Vec<f64>> = match &input.normals {
1226 MeshNormalsInput::None => Vec::new(),
1227 MeshNormalsInput::Uniform(n) => vec![n.to_vec()],
1228 MeshNormalsInput::PerVertex(rows) => rows.iter().map(|n| n.to_vec()).collect(),
1229 };
1230 let to_ix = |v: usize| i64::try_from(v + 1).unwrap_or(i64::MAX);
1234 let triangle_strips: Vec<Vec<i64>> = input
1235 .triangles
1236 .iter()
1237 .map(|&[p, q, r]| vec![to_ix(p), to_ix(r), to_ix(q)])
1238 .collect();
1239 let face = a.add_complex_triangulated_face(
1240 name.to_owned(),
1241 m::CoordinatesListRef::CoordinatesList(coordinates),
1242 npoints,
1243 normals,
1244 None,
1245 Vec::new(),
1246 triangle_strips,
1247 Vec::new(),
1248 )?;
1249 let solid = a.add_tessellated_solid(
1250 name.to_owned(),
1251 vec![m::TessellatedStructuredItemRef::ComplexTriangulatedFace(
1252 face,
1253 )],
1254 of_solid.map(m::ManifoldSolidBrepRef::ManifoldSolidBrep),
1255 )?;
1256 self.parts[part.0].meshes.push(solid);
1257 Ok(solid)
1258 }
1259
1260 pub fn person_and_org(
1267 &mut self,
1268 p: &PersonOrg,
1269 ) -> Result<m::PersonAndOrganizationId, AuthorError> {
1270 let a = &mut self.author;
1271 let person = a.add_person(
1272 p.person_id.clone(),
1273 p.last_name.clone(),
1274 p.first_name.clone(),
1275 None,
1276 None,
1277 None,
1278 )?;
1279 let organization = a.add_organization(None, p.organization.clone(), None)?;
1280 a.add_person_and_organization(
1281 m::PersonRef::Person(person),
1282 m::OrganizationRef::Organization(organization),
1283 )
1284 }
1285
1286 pub fn contributor(
1294 &mut self,
1295 part: Part,
1296 role: &str,
1297 who: m::PersonAndOrganizationId,
1298 ) -> Result<(), AuthorError> {
1299 let a = &mut self.author;
1300 let role = a.add_person_and_organization_role(role.to_owned())?;
1301 a.add_applied_person_and_organization_assignment(
1302 m::PersonAndOrganizationRef::PersonAndOrganization(who),
1303 m::PersonAndOrganizationRoleRef::PersonAndOrganizationRole(role),
1304 vec![m::PersonAndOrganizationItemRef::ProductDefinitionFormation(
1305 self.parts[part.0].formation,
1306 )],
1307 )?;
1308 Ok(())
1309 }
1310
1311 pub fn approve(
1319 &mut self,
1320 part: Part,
1321 input: &ApprovalInput,
1322 ) -> Result<m::ApprovalId, AuthorError> {
1323 let a = &mut self.author;
1324 let status = a.add_approval_status(input.status.clone())?;
1325 let approval = a.add_approval(
1326 m::ApprovalStatusRef::ApprovalStatus(status),
1327 input.level.clone(),
1328 )?;
1329 a.add_applied_approval_assignment(
1330 m::ApprovalRef::Approval(approval),
1331 vec![m::ApprovalItemRef::ProductDefinitionFormation(
1332 self.parts[part.0].formation,
1333 )],
1334 )?;
1335 for (who, role) in &input.approvers {
1336 let role = a.add_approval_role(role.clone())?;
1337 a.add_approval_person_organization(
1338 m::PersonOrganizationSelectRef::PersonAndOrganization(*who),
1339 m::ApprovalRef::Approval(approval),
1340 m::ApprovalRoleRef::ApprovalRole(role),
1341 )?;
1342 }
1343 if let Some(date) = input.date {
1344 let calendar = a.add_calendar_date(date.year, date.day, date.month)?;
1345 let zone = a.add_coordinated_universal_time_offset(0, None, m::AheadOrBehind::Exact)?;
1346 let time = a.add_local_time(
1347 date.hour,
1348 date.minute,
1349 None,
1350 m::CoordinatedUniversalTimeOffsetRef::CoordinatedUniversalTimeOffset(zone),
1351 )?;
1352 let date_time = a.add_date_and_time(
1353 m::DateRef::CalendarDate(calendar),
1354 m::LocalTimeRef::LocalTime(time),
1355 )?;
1356 a.add_approval_date_time(
1357 m::DateTimeSelectRef::DateAndTime(date_time),
1358 m::ApprovalRef::Approval(approval),
1359 )?;
1360 }
1361 Ok(approval)
1362 }
1363
1364 pub fn document(&mut self, part: Part, d: &DocumentInput) -> Result<(), AuthorError> {
1370 let a = &mut self.author;
1371 let kind = a.add_document_type(d.kind.clone())?;
1372 let doc = a.add_document(
1373 d.id.clone(),
1374 d.name.clone(),
1375 d.description.clone(),
1376 m::DocumentTypeRef::DocumentType(kind),
1377 )?;
1378 a.add_applied_document_reference(
1379 m::DocumentRef::Document(doc),
1380 String::new(),
1381 vec![m::DocumentReferenceItemRef::ProductDefinitionFormation(
1382 self.parts[part.0].formation,
1383 )],
1384 )?;
1385 Ok(())
1386 }
1387
1388 pub fn security(
1394 &mut self,
1395 part: Part,
1396 name: &str,
1397 purpose: &str,
1398 level: &str,
1399 ) -> Result<(), AuthorError> {
1400 let a = &mut self.author;
1401 let level = a.add_security_classification_level(level.to_owned())?;
1402 let classification = a.add_security_classification(
1403 name.to_owned(),
1404 purpose.to_owned(),
1405 m::SecurityClassificationLevelRef::SecurityClassificationLevel(level),
1406 )?;
1407 a.add_applied_security_classification_assignment(
1408 m::SecurityClassificationRef::SecurityClassification(classification),
1409 vec![
1410 m::SecurityClassificationItemRef::ProductDefinitionFormation(
1411 self.parts[part.0].formation,
1412 ),
1413 ],
1414 )?;
1415 Ok(())
1416 }
1417
1418 pub fn place(
1427 &mut self,
1428 parent: Part,
1429 child: Part,
1430 at: Frame,
1431 ) -> Result<m::NextAssemblyUsageOccurrenceId, AuthorError> {
1432 let nauo = self.author.add_next_assembly_usage_occurrence(
1433 format!("{}", self.placements.len() + 1),
1434 String::new(),
1435 None,
1436 m::ProductDefinitionOrReferenceRef::ProductDefinition(self.parts[parent.0].definition),
1437 m::ProductDefinitionOrReferenceRef::ProductDefinition(self.parts[child.0].definition),
1438 None,
1439 )?;
1440 let to = self.placement(&at)?;
1441 self.placements.push(PendingPlacement {
1442 nauo,
1443 parent: parent.0,
1444 child: child.0,
1445 to,
1446 });
1447 Ok(nauo)
1448 }
1449
1450 pub fn finish(mut self) -> Result<String, AuthorError> {
1458 let a = &mut self.author;
1459 let mut part_reps: Vec<m::RepresentationOrRepresentationReferenceRef> =
1463 Vec::with_capacity(self.parts.len());
1464 for part in &self.parts {
1465 let mut items = vec![m::RepresentationItemRef::Axis2Placement3d(part.origin)];
1466 items.extend(part.items.iter().cloned());
1467 let has_solid = part
1468 .items
1469 .iter()
1470 .any(|i| matches!(i, m::RepresentationItemRef::ManifoldSolidBrep(_)));
1471 let (rep, rep_or_ref) = if has_solid {
1472 let id = a.add_advanced_brep_shape_representation(
1473 String::new(),
1474 items,
1475 m::RepresentationContextRef::Complex(self.ctx),
1476 )?;
1477 (
1478 m::RepresentationRef::AdvancedBrepShapeRepresentation(id),
1479 m::RepresentationOrRepresentationReferenceRef::AdvancedBrepShapeRepresentation(
1480 id,
1481 ),
1482 )
1483 } else {
1484 let id = a.add_shape_representation(
1485 String::new(),
1486 items,
1487 m::RepresentationContextRef::Complex(self.ctx),
1488 )?;
1489 (
1490 m::RepresentationRef::ShapeRepresentation(id),
1491 m::RepresentationOrRepresentationReferenceRef::ShapeRepresentation(id),
1492 )
1493 };
1494 a.add_shape_definition_representation(
1495 m::RepresentedDefinitionRef::ProductDefinitionShape(part.shape),
1496 rep,
1497 )?;
1498 part_reps.push(rep_or_ref);
1499
1500 if !part.meshes.is_empty() {
1503 let tsr = a.add_tessellated_shape_representation(
1504 String::new(),
1505 part.meshes
1506 .iter()
1507 .map(|&t| m::RepresentationItemRef::TessellatedSolid(t))
1508 .collect(),
1509 m::RepresentationContextRef::Complex(self.ctx),
1510 )?;
1511 a.add_shape_definition_representation(
1512 m::RepresentedDefinitionRef::ProductDefinitionShape(part.shape),
1513 m::RepresentationRef::TessellatedShapeRepresentation(tsr),
1514 )?;
1515 }
1516 }
1517 bind_placements(a, &self.parts, &self.placements, &part_reps)?;
1519 if !self.parts.is_empty() {
1520 a.add_product_related_product_category(
1521 "part".to_owned(),
1522 None,
1523 self.parts
1524 .iter()
1525 .map(|p| m::ProductRef::Product(p.product))
1526 .collect(),
1527 )?;
1528 }
1529 if !self.hidden.is_empty() {
1532 a.add_invisibility(std::mem::take(&mut self.hidden))?;
1533 }
1534 if !self.styles.is_empty() {
1536 a.add_mechanical_design_geometric_presentation_representation(
1537 String::new(),
1538 self.styles
1539 .iter()
1540 .map(|&s| m::RepresentationItemRef::StyledItem(s))
1541 .collect(),
1542 m::RepresentationContextRef::Complex(self.ctx),
1543 )?;
1544 }
1545 let h = &self.header;
1546 let file_header = FileHeader {
1547 description: h.description.clone().unwrap_or_default(),
1548 file_name: h.file_name.clone().unwrap_or_default(),
1549 time_stamp: h.timestamp.clone().unwrap_or_else(now_iso_utc),
1550 authors: h.authors.clone(),
1551 organizations: h.organizations.clone(),
1552 preprocessor_version: concat!("step-io ", env!("CARGO_PKG_VERSION")).to_owned(),
1553 originating_system: h.originating_system.clone().unwrap_or_default(),
1554 authorisation: h.authorisation.clone().unwrap_or_default(),
1555 };
1556 Ok(self.author.finish_with_header(&file_header))
1557 }
1558}
1559
1560fn bind_placements(
1566 a: &mut Ap242Author,
1567 parts: &[PendingPart],
1568 placements: &[PendingPlacement],
1569 part_reps: &[m::RepresentationOrRepresentationReferenceRef],
1570) -> Result<(), AuthorError> {
1571 for placement in placements {
1572 let pds = a.add_product_definition_shape(
1573 "Placement".to_owned(),
1574 Some("Placement of an item".to_owned()),
1575 m::CharacterizedDefinitionRef::NextAssemblyUsageOccurrence(placement.nauo),
1576 )?;
1577 let idt = a.add_item_defined_transformation(
1578 String::new(),
1579 None,
1580 m::RepresentationItemRef::Axis2Placement3d(parts[placement.parent].origin),
1581 m::RepresentationItemRef::Axis2Placement3d(placement.to),
1582 )?;
1583 let rrwt = a.add_complex(vec![
1584 m::UnitPart::RepresentationRelationship {
1585 name: String::new(),
1586 description: None,
1587 rep_1: part_reps[placement.child].clone(),
1588 rep_2: part_reps[placement.parent].clone(),
1589 },
1590 m::UnitPart::RepresentationRelationshipWithTransformation {
1591 transformation_operator: m::TransformationRef::ItemDefinedTransformation(idt),
1592 },
1593 m::UnitPart::ShapeRepresentationRelationship,
1594 ])?;
1595 a.add_context_dependent_shape_representation(
1596 m::ShapeRepresentationRelationshipRef::Complex(rrwt),
1597 m::ProductDefinitionShapeRef::ProductDefinitionShape(pds),
1598 )?;
1599 }
1600 Ok(())
1601}
1602
1603#[cfg(test)]
1604mod tests {
1605 use super::iso_utc_from_unix;
1606
1607 #[test]
1608 fn iso_utc_from_unix_known_values() {
1609 assert_eq!(iso_utc_from_unix(0), "1970-01-01T00:00:00Z");
1610 assert_eq!(iso_utc_from_unix(1_709_251_199), "2024-02-29T23:59:59Z");
1612 assert_eq!(iso_utc_from_unix(1_709_251_200), "2024-03-01T00:00:00Z");
1614 }
1615}