Skip to main content

cadmpeg_codec_step/
options.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Writer header metadata and target-schema selection.
3
4/// Metadata written to the STEP `FILE_NAME` header record.
5///
6/// Default values produce deterministic output. They identify the file as
7/// `cadmpeg_model`, leave the author and organization empty, use `cadmpeg` as
8/// the originating system, and substitute `1970-01-01T00:00:00` for the empty
9/// timestamp.
10#[derive(Debug, Clone)]
11pub struct StepWriteOptions {
12    /// Application protocol and edition declared by `FILE_SCHEMA`.
13    pub schema: StepSchema,
14    /// Handling of IR content the selected writer cannot represent exactly.
15    pub unsupported: StepUnsupportedPolicy,
16    /// The `FILE_NAME` name field.
17    ///
18    /// The STEP `PRODUCT` id and name come from the first IR body name, or
19    /// `cadmpeg_model` when that body has no name.
20    pub product_name: String,
21    /// The sole entry in the `FILE_NAME` author list.
22    pub author: String,
23    /// The sole entry in the `FILE_NAME` organization list.
24    pub organization: String,
25    /// The `FILE_NAME` timestamp.
26    ///
27    /// Supply an ISO 8601 value. An empty string is written as
28    /// `1970-01-01T00:00:00`.
29    pub timestamp: String,
30    /// The `FILE_NAME` originating-system field.
31    pub originating_system: String,
32}
33
34impl Default for StepWriteOptions {
35    fn default() -> Self {
36        StepWriteOptions {
37            schema: StepSchema::Ap214,
38            unsupported: StepUnsupportedPolicy::Report,
39            product_name: "cadmpeg_model".to_string(),
40            author: String::new(),
41            organization: String::new(),
42            timestamp: String::new(),
43            originating_system: "cadmpeg".to_string(),
44        }
45    }
46}
47
48/// Policy for semantic content not representable by the selected STEP target.
49#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
50pub enum StepUnsupportedPolicy {
51    /// Emit the representable subset and return machine-readable loss notes.
52    #[default]
53    Report,
54    /// Reject the document before writing any output byte.
55    Reject,
56}
57
58/// STEP application-protocol targets supported by the Part 21 writer.
59///
60/// The AP242 edition number and the long-form schema revision are distinct:
61/// editions 1, 2, and 3 use long-form revisions 1, 3, and 4 respectively.
62#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
63pub enum StepSchema {
64    /// AP203 edition 1 `CONFIG_CONTROL_DESIGN`.
65    Ap203Edition1,
66    /// AP203 edition 2 modular long form.
67    Ap203Edition2,
68    /// AP214 `AUTOMOTIVE_DESIGN`.
69    #[default]
70    Ap214,
71    /// AP242 edition 1 modular long form.
72    Ap242Edition1,
73    /// AP242 edition 2 modular long form.
74    Ap242Edition2,
75    /// AP242 edition 3 modular long form.
76    Ap242Edition3,
77}
78
79impl StepSchema {
80    /// Exact schema identifier written in `FILE_SCHEMA`.
81    pub const fn file_schema(self) -> &'static str {
82        match self {
83            Self::Ap203Edition1 => "CONFIG_CONTROL_DESIGN",
84            Self::Ap203Edition2 => "AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF { 1 0 10303 403 2 1 2 }",
85            Self::Ap214 => "AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }",
86            Self::Ap242Edition1 => "AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF { 1 0 10303 442 1 1 4 }",
87            Self::Ap242Edition2 => "AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF { 1 0 10303 442 3 1 4 }",
88            Self::Ap242Edition3 => "AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF { 1 0 10303 442 4 1 4 }",
89        }
90    }
91
92    pub(crate) fn ap242_edition(identifier: &str) -> Option<&'static str> {
93        const NAME: &str = "AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF";
94        let (name, oid) = split_schema_identifier(identifier)?;
95        if !name.eq_ignore_ascii_case(NAME) {
96            return None;
97        }
98        match oid.as_deref() {
99            Some([1, 0, 10303, 442, 1, 1, 4]) => Some("edition 1"),
100            Some([1, 0, 10303, 442, 3, 1, 4]) => Some("edition 2"),
101            Some([1, 0, 10303, 442, 4, 1, 4]) => Some("edition 3"),
102            _ => None,
103        }
104    }
105
106    pub(crate) const fn supports_tessellation(self) -> bool {
107        matches!(
108            self,
109            Self::Ap242Edition1 | Self::Ap242Edition2 | Self::Ap242Edition3
110        )
111    }
112
113    pub(crate) const fn supports_semantic_pmi(self) -> bool {
114        self.supports_tessellation()
115    }
116
117    pub(crate) const fn supports_visibility(self) -> bool {
118        !matches!(self, Self::Ap203Edition1)
119    }
120
121    pub(crate) const fn application_protocol(self) -> (&'static str, &'static str, i32) {
122        match self {
123            Self::Ap203Edition1 => (
124                "configuration controlled 3d designs of mechanical parts and assemblies",
125                "config_control_design",
126                1994,
127            ),
128            Self::Ap203Edition2 => (
129                "configuration controlled 3d designs of mechanical parts and assemblies",
130                "ap203_configuration_controlled_3d_design_of_mechanical_parts_and_assemblies",
131                2011,
132            ),
133            Self::Ap214 => ("automotive design", "automotive_design", 2000),
134            Self::Ap242Edition1 => (
135                "managed model based 3d engineering",
136                "ap242_managed_model_based_3d_engineering",
137                2014,
138            ),
139            Self::Ap242Edition2 => (
140                "managed model based 3d engineering",
141                "ap242_managed_model_based_3d_engineering",
142                2020,
143            ),
144            Self::Ap242Edition3 => (
145                "managed model based 3d engineering",
146                "ap242_managed_model_based_3d_engineering",
147                2022,
148            ),
149        }
150    }
151}
152
153fn split_schema_identifier(identifier: &str) -> Option<(&str, Option<Vec<i64>>)> {
154    let identifier = identifier.trim();
155    let Some(open) = identifier.rfind('{') else {
156        return Some((identifier, None));
157    };
158    if !identifier.ends_with('}') || identifier[..open].trim().is_empty() {
159        return None;
160    }
161    let oid = identifier[open + 1..identifier.len() - 1]
162        .split_whitespace()
163        .map(str::parse::<i64>)
164        .collect::<Result<Vec<i64>, _>>()
165        .ok()?;
166    (!oid.is_empty()).then(|| (identifier[..open].trim_end(), Some(oid)))
167}