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