Skip to main content

cadmpeg_codec_step/
options.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Writer targets and header metadata.
3//!
4//! These configure *how* a target is written. Which target is written is the
5//! export request's answer, resolved against the source: `StepCodec::plan`
6//! takes it from `TargetRequest`.
7
8use cadmpeg_core::dialect::DialectId;
9use cadmpeg_core::target::TargetDescriptor;
10
11use crate::dialect::{
12    STEP_AP203_E1, STEP_AP203_E2, STEP_AP214, STEP_AP242_E1, STEP_AP242_E2, STEP_AP242_E3,
13};
14
15/// Metadata written to the STEP `FILE_NAME` header record.
16///
17/// Default values produce deterministic output. They identify the file as
18/// `cadmpeg_model`, leave the author and organization empty, use `cadmpeg` as
19/// the originating system, and substitute `1970-01-01T00:00:00` when the
20/// timestamp is absent.
21#[derive(Debug, Clone)]
22pub struct StepWriteOptions {
23    /// The `FILE_NAME` name field.
24    ///
25    /// The STEP `PRODUCT` id and name come from the first IR body name, or
26    /// `cadmpeg_model` when that body has no name.
27    pub product_name: String,
28    /// The sole entry in the `FILE_NAME` author list.
29    pub author: String,
30    /// The sole entry in the `FILE_NAME` organization list.
31    pub organization: String,
32    /// The `FILE_NAME` timestamp.
33    ///
34    /// Supply an ISO 8601 value. Absence writes `1970-01-01T00:00:00`.
35    pub timestamp: Option<String>,
36    /// The `FILE_NAME` originating-system field.
37    pub originating_system: String,
38}
39
40impl Default for StepWriteOptions {
41    fn default() -> Self {
42        StepWriteOptions {
43            product_name: "cadmpeg_model".to_string(),
44            author: String::new(),
45            organization: String::new(),
46            timestamp: None,
47            originating_system: "cadmpeg".to_string(),
48        }
49    }
50}
51
52/// STEP application-protocol targets supported by the Part 21 writer.
53///
54/// The AP242 edition number and the long-form schema revision are distinct:
55/// editions 1, 2, and 3 use long-form revisions 1, 3, and 4 respectively.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
57pub enum StepSchema {
58    /// AP203 edition 1 `CONFIG_CONTROL_DESIGN`.
59    Ap203Edition1,
60    /// AP203 edition 2 modular long form.
61    Ap203Edition2,
62    /// AP214 `AUTOMOTIVE_DESIGN`.
63    Ap214,
64    /// AP242 edition 1 modular long form.
65    Ap242Edition1,
66    /// AP242 edition 2 modular long form.
67    Ap242Edition2,
68    /// AP242 edition 3 modular long form.
69    Ap242Edition3,
70}
71
72macro_rules! writer_vocabulary {
73    ($(#[$all_meta:meta])* $count:literal; $($variant:ident),+ $(,)?) => {
74        $(#[$all_meta])*
75        pub(crate) const ALL: [Self; $count] = [$(Self::$variant),+];
76        /// The generic encoder view projected from [`Self::ALL`].
77        pub(crate) const TARGETS: &'static [TargetDescriptor] = &[
78            $(Self::$variant.descriptor()),+
79        ];
80        /// The schema at a generic encoder target index.
81        pub(crate) const fn from_target_index(index: usize) -> Option<Self> {
82            if index < Self::ALL.len() {
83                Some(Self::ALL[index])
84            } else {
85                None
86            }
87        }
88    };
89}
90
91impl StepSchema {
92    writer_vocabulary!(
93        /// Every schema the Part 21 writer can emit, and so every row of the
94        /// synthesis catalog. The same invocation projects the generic encoder
95        /// catalog, so adding a typed schema cannot omit its target descriptor.
96        /// Every row names the exact `FILE_SCHEMA` declaration this enum writes;
97        /// AP214 is the cross-format default.
98        6;
99        Ap203Edition1,
100        Ap203Edition2,
101        Ap214,
102        Ap242Edition1,
103        Ap242Edition2,
104        Ap242Edition3,
105    );
106
107    /// Exact schema identifier written in `FILE_SCHEMA`.
108    pub const fn file_schema(self) -> &'static str {
109        match self {
110            Self::Ap203Edition1 => "CONFIG_CONTROL_DESIGN",
111            Self::Ap203Edition2 => "AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF { 1 0 10303 403 2 1 2 }",
112            Self::Ap214 => "AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }",
113            Self::Ap242Edition1 => "AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF { 1 0 10303 442 1 1 4 }",
114            Self::Ap242Edition2 => "AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF { 1 0 10303 442 3 1 4 }",
115            Self::Ap242Edition3 => "AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF { 1 0 10303 442 4 1 4 }",
116        }
117    }
118
119    /// The typed dialect identity written for this schema.
120    pub(crate) const fn id(self) -> DialectId {
121        match self {
122            Self::Ap203Edition1 => STEP_AP203_E1,
123            Self::Ap203Edition2 => STEP_AP203_E2,
124            Self::Ap214 => STEP_AP214,
125            Self::Ap242Edition1 => STEP_AP242_E1,
126            Self::Ap242Edition2 => STEP_AP242_E2,
127            Self::Ap242Edition3 => STEP_AP242_E3,
128        }
129    }
130
131    /// The typed write-target catalog row for this schema.
132    #[must_use]
133    pub const fn descriptor(self) -> TargetDescriptor {
134        let aliases = match self {
135            Self::Ap203Edition1 => &["ap203e1"].as_slice(),
136            Self::Ap203Edition2 => &["ap203e2"].as_slice(),
137            Self::Ap214 => &["ap214"].as_slice(),
138            Self::Ap242Edition1 => &["ap242e1"].as_slice(),
139            Self::Ap242Edition2 => &["ap242e2"].as_slice(),
140            Self::Ap242Edition3 => &["ap242e3"].as_slice(),
141        };
142        TargetDescriptor {
143            id: self.id(),
144            aliases,
145        }
146    }
147
148    pub(crate) const fn supports_tessellation(self) -> bool {
149        matches!(
150            self,
151            Self::Ap242Edition1 | Self::Ap242Edition2 | Self::Ap242Edition3
152        )
153    }
154
155    pub(crate) const fn supports_semantic_pmi(self) -> bool {
156        self.supports_tessellation()
157    }
158
159    pub(crate) const fn supports_visibility(self) -> bool {
160        !matches!(self, Self::Ap203Edition1)
161    }
162
163    pub(crate) const fn application_protocol(self) -> (&'static str, &'static str, i32) {
164        match self {
165            Self::Ap203Edition1 => (
166                "configuration controlled 3d designs of mechanical parts and assemblies",
167                "config_control_design",
168                1994,
169            ),
170            Self::Ap203Edition2 => (
171                "configuration controlled 3d designs of mechanical parts and assemblies",
172                "ap203_configuration_controlled_3d_design_of_mechanical_parts_and_assemblies",
173                2011,
174            ),
175            Self::Ap214 => ("automotive design", "automotive_design", 2000),
176            Self::Ap242Edition1 => (
177                "managed model based 3d engineering",
178                "ap242_managed_model_based_3d_engineering",
179                2014,
180            ),
181            Self::Ap242Edition2 => (
182                "managed model based 3d engineering",
183                "ap242_managed_model_based_3d_engineering",
184                2020,
185            ),
186            Self::Ap242Edition3 => (
187                "managed model based 3d engineering",
188                "ap242_managed_model_based_3d_engineering",
189                2022,
190            ),
191        }
192    }
193}