use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use crate::extension::ExtensionRegistry;
use super::beam_lattice::BeamSet;
use super::boolean_ops::BooleanShape;
use super::production::ProductionInfo;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Extension {
Core,
Material,
Production,
Slice,
BeamLattice,
SecureContent,
BooleanOperations,
Displacement,
Volumetric,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SpecConformance {
#[default]
Strict,
Lenient,
}
impl Extension {
pub fn namespace(&self) -> &'static str {
match self {
Extension::Core => "http://schemas.microsoft.com/3dmanufacturing/core/2015/02",
Extension::Material => "http://schemas.microsoft.com/3dmanufacturing/material/2015/02",
Extension::Production => {
"http://schemas.microsoft.com/3dmanufacturing/production/2015/06"
}
Extension::Slice => "http://schemas.microsoft.com/3dmanufacturing/slice/2015/07",
Extension::BeamLattice => {
"http://schemas.microsoft.com/3dmanufacturing/beamlattice/2017/02"
}
Extension::SecureContent => {
"http://schemas.microsoft.com/3dmanufacturing/securecontent/2019/07"
}
Extension::BooleanOperations => {
"http://schemas.3mf.io/3dmanufacturing/booleanoperations/2023/07"
}
Extension::Displacement => {
"http://schemas.microsoft.com/3dmanufacturing/displacement/2022/07"
}
Extension::Volumetric => "http://schemas.3mf.io/volumetric/2023/02",
}
}
pub fn from_namespace(namespace: &str) -> Option<Self> {
match namespace {
"http://schemas.microsoft.com/3dmanufacturing/core/2015/02" => Some(Extension::Core),
"http://schemas.microsoft.com/3dmanufacturing/material/2015/02" => {
Some(Extension::Material)
}
"http://schemas.microsoft.com/3dmanufacturing/production/2015/06" => {
Some(Extension::Production)
}
"http://schemas.microsoft.com/3dmanufacturing/slice/2015/07" => Some(Extension::Slice),
"http://schemas.microsoft.com/3dmanufacturing/beamlattice/2017/02" => {
Some(Extension::BeamLattice)
}
"http://schemas.microsoft.com/3dmanufacturing/beamlattice/balls/2020/07" => {
Some(Extension::BeamLattice)
}
"http://schemas.microsoft.com/3dmanufacturing/securecontent/2019/07" => {
Some(Extension::SecureContent)
}
"http://schemas.microsoft.com/3dmanufacturing/securecontent/2019/04" => {
Some(Extension::SecureContent)
}
"http://schemas.3mf.io/3dmanufacturing/booleanoperations/2023/07" => {
Some(Extension::BooleanOperations)
}
"http://schemas.microsoft.com/3dmanufacturing/displacement/2022/07" => {
Some(Extension::Displacement)
}
"http://schemas.3mf.io/3dmanufacturing/displacement/2023/10" => {
Some(Extension::Displacement)
}
"http://schemas.3mf.io/volumetric/2023/02" => Some(Extension::Volumetric),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
Extension::Core => "Core",
Extension::Material => "Material",
Extension::Production => "Production",
Extension::Slice => "Slice",
Extension::BeamLattice => "BeamLattice",
Extension::SecureContent => "SecureContent",
Extension::BooleanOperations => "BooleanOperations",
Extension::Displacement => "Displacement",
Extension::Volumetric => "Volumetric",
}
}
}
#[derive(Clone)]
pub struct ParserConfig {
supported_extensions: HashSet<Extension>,
custom_extensions: HashMap<String, CustomExtensionInfo>,
key_provider: Option<Arc<dyn crate::key_provider::KeyProvider>>,
registry: ExtensionRegistry,
spec_conformance: SpecConformance,
}
impl ParserConfig {
pub fn new() -> Self {
let mut supported = HashSet::new();
supported.insert(Extension::Core);
Self {
supported_extensions: supported,
custom_extensions: HashMap::new(),
key_provider: None,
registry: ExtensionRegistry::new(),
spec_conformance: SpecConformance::Strict,
}
}
pub fn with_all_extensions() -> Self {
let mut supported = HashSet::new();
supported.insert(Extension::Core);
supported.insert(Extension::Material);
supported.insert(Extension::Production);
supported.insert(Extension::Slice);
supported.insert(Extension::BeamLattice);
supported.insert(Extension::SecureContent);
supported.insert(Extension::BooleanOperations);
supported.insert(Extension::Displacement);
supported.insert(Extension::Volumetric);
Self {
supported_extensions: supported,
custom_extensions: HashMap::new(),
key_provider: None,
registry: crate::extensions::create_default_registry(),
spec_conformance: SpecConformance::Strict,
}
}
pub fn with_extension(mut self, extension: Extension) -> Self {
self.supported_extensions.insert(extension);
match extension {
Extension::Material => {
self.registry
.register(Arc::new(crate::extensions::MaterialExtensionHandler));
}
Extension::Production => {
self.registry
.register(Arc::new(crate::extensions::ProductionExtensionHandler));
}
Extension::Slice => {
self.registry
.register(Arc::new(crate::extensions::SliceExtensionHandler));
}
Extension::BeamLattice => {
self.registry
.register(Arc::new(crate::extensions::BeamLatticeExtensionHandler));
}
Extension::SecureContent => {
self.registry
.register(Arc::new(crate::extensions::SecureContentExtensionHandler));
}
Extension::BooleanOperations => {
self.registry.register(Arc::new(
crate::extensions::BooleanOperationsExtensionHandler,
));
}
Extension::Displacement => {
self.registry
.register(Arc::new(crate::extensions::DisplacementExtensionHandler));
}
Extension::Volumetric => {
self.registry
.register(Arc::new(crate::extensions::VolumetricExtensionHandler));
}
Extension::Core => {
}
}
self
}
pub fn with_custom_extension(
mut self,
namespace: impl Into<String>,
name: impl Into<String>,
) -> Self {
let namespace = namespace.into();
let name = name.into();
self.custom_extensions.insert(
namespace.clone(),
CustomExtensionInfo {
namespace,
name,
element_handler: None,
validation_handler: None,
},
);
self
}
pub fn with_custom_extension_handler(
mut self,
namespace: impl Into<String>,
name: impl Into<String>,
handler: CustomElementHandler,
) -> Self {
let namespace = namespace.into();
let name = name.into();
self.custom_extensions.insert(
namespace.clone(),
CustomExtensionInfo {
namespace,
name,
element_handler: Some(handler),
validation_handler: None,
},
);
self
}
pub fn with_custom_extension_handlers(
mut self,
namespace: impl Into<String>,
name: impl Into<String>,
element_handler: CustomElementHandler,
validation_handler: CustomValidationHandler,
) -> Self {
let namespace = namespace.into();
let name = name.into();
self.custom_extensions.insert(
namespace.clone(),
CustomExtensionInfo {
namespace,
name,
element_handler: Some(element_handler),
validation_handler: Some(validation_handler),
},
);
self
}
pub fn supports(&self, extension: &Extension) -> bool {
self.supported_extensions.contains(extension)
}
pub fn with_key_provider(
mut self,
provider: Arc<dyn crate::key_provider::KeyProvider>,
) -> Self {
self.key_provider = Some(provider);
self
}
pub fn key_provider(&self) -> Option<&Arc<dyn crate::key_provider::KeyProvider>> {
self.key_provider.as_ref()
}
pub fn has_custom_extension(&self, namespace: &str) -> bool {
self.custom_extensions.contains_key(namespace)
}
pub fn supported_extensions(&self) -> &HashSet<Extension> {
&self.supported_extensions
}
pub fn get_custom_extension(&self, namespace: &str) -> Option<&CustomExtensionInfo> {
self.custom_extensions.get(namespace)
}
pub fn custom_extensions(&self) -> &HashMap<String, CustomExtensionInfo> {
&self.custom_extensions
}
pub fn with_extension_handler(
mut self,
handler: Arc<dyn crate::extension::ExtensionHandler>,
) -> Self {
self.registry.register(handler);
self
}
pub fn registry(&self) -> &ExtensionRegistry {
&self.registry
}
pub fn registry_mut(&mut self) -> &mut ExtensionRegistry {
&mut self.registry
}
pub fn with_spec_conformance(mut self, conformance: SpecConformance) -> Self {
self.spec_conformance = conformance;
self
}
pub fn spec_conformance(&self) -> SpecConformance {
self.spec_conformance
}
pub(crate) fn is_lenient(&self) -> bool {
self.spec_conformance == SpecConformance::Lenient
}
}
impl Default for ParserConfig {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for ParserConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ParserConfig")
.field("supported_extensions", &self.supported_extensions)
.field("custom_extensions_count", &self.custom_extensions.len())
.field("spec_conformance", &self.spec_conformance)
.finish()
}
}
impl std::fmt::Debug for CustomExtensionInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CustomExtensionInfo")
.field("namespace", &self.namespace)
.field("name", &self.name)
.field("has_element_handler", &self.element_handler.is_some())
.field("has_validation_handler", &self.validation_handler.is_some())
.finish()
}
}
#[derive(Debug, Clone)]
pub struct CustomExtensionContext {
pub element_name: String,
pub namespace: String,
pub attributes: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub enum CustomElementResult {
Handled,
NotHandled,
}
pub type CustomElementHandler =
Arc<dyn Fn(&CustomExtensionContext) -> Result<CustomElementResult, String> + Send + Sync>;
pub type CustomValidationHandler = Arc<dyn Fn(&Model) -> Result<(), String> + Send + Sync>;
#[derive(Clone)]
pub struct CustomExtensionInfo {
pub namespace: String,
pub name: String,
pub element_handler: Option<CustomElementHandler>,
pub validation_handler: Option<CustomValidationHandler>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Vertex {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vertex {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Triangle {
pub v1: usize,
pub v2: usize,
pub v3: usize,
pub pid: Option<usize>,
pub pindex: Option<usize>,
pub p1: Option<usize>,
pub p2: Option<usize>,
pub p3: Option<usize>,
}
impl Triangle {
pub fn new(v1: usize, v2: usize, v3: usize) -> Self {
Self {
v1,
v2,
v3,
pid: None,
pindex: None,
p1: None,
p2: None,
p3: None,
}
}
pub fn with_material(v1: usize, v2: usize, v3: usize, pid: usize) -> Self {
Self {
v1,
v2,
v3,
pid: Some(pid),
pindex: None,
p1: None,
p2: None,
p3: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Mesh {
pub vertices: Vec<Vertex>,
pub triangles: Vec<Triangle>,
pub beamset: Option<BeamSet>,
}
impl Mesh {
pub fn new() -> Self {
Self {
vertices: Vec::new(),
triangles: Vec::new(),
beamset: None,
}
}
pub fn with_capacity(vertices: usize, triangles: usize) -> Self {
Self {
vertices: Vec::with_capacity(vertices),
triangles: Vec::with_capacity(triangles),
beamset: None,
}
}
}
impl Default for Mesh {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DisplacementTriangle {
pub v1: usize,
pub v2: usize,
pub v3: usize,
pub pid: Option<usize>,
pub pindex: Option<usize>,
pub p1: Option<usize>,
pub p2: Option<usize>,
pub p3: Option<usize>,
pub did: Option<usize>,
pub d1: Option<usize>,
pub d2: Option<usize>,
pub d3: Option<usize>,
}
impl DisplacementTriangle {
pub fn new(v1: usize, v2: usize, v3: usize) -> Self {
Self {
v1,
v2,
v3,
pid: None,
pindex: None,
p1: None,
p2: None,
p3: None,
did: None,
d1: None,
d2: None,
d3: None,
}
}
}
#[derive(Debug, Clone)]
pub struct DisplacementMesh {
pub vertices: Vec<Vertex>,
pub triangles: Vec<DisplacementTriangle>,
}
impl DisplacementMesh {
pub fn new() -> Self {
Self {
vertices: Vec::new(),
triangles: Vec::new(),
}
}
}
impl Default for DisplacementMesh {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Component {
pub objectid: usize,
pub transform: Option<[f64; 12]>,
pub path: Option<String>,
pub production: Option<ProductionInfo>,
}
impl Component {
pub fn new(objectid: usize) -> Self {
Self {
objectid,
transform: None,
path: None,
production: None,
}
}
pub fn with_transform(objectid: usize, transform: [f64; 12]) -> Self {
Self {
objectid,
transform: Some(transform),
path: None,
production: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Object {
pub id: usize,
pub name: Option<String>,
pub object_type: ObjectType,
pub mesh: Option<Mesh>,
pub displacement_mesh: Option<DisplacementMesh>,
pub pid: Option<usize>,
pub pindex: Option<usize>,
pub basematerialid: Option<usize>,
pub slicestackid: Option<usize>,
pub production: Option<ProductionInfo>,
pub boolean_shape: Option<BooleanShape>,
pub components: Vec<Component>,
pub(crate) has_thumbnail_attribute: bool,
pub(crate) has_extension_shapes: bool,
#[doc(hidden)]
pub parse_order: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ObjectType {
Model,
Support,
SolidSupport,
Surface,
Other,
}
impl Object {
pub fn new(id: usize) -> Self {
Self {
id,
name: None,
object_type: ObjectType::Model,
mesh: None,
displacement_mesh: None,
pid: None,
pindex: None,
basematerialid: None,
slicestackid: None,
production: None,
boolean_shape: None,
components: Vec::new(),
has_thumbnail_attribute: false,
has_extension_shapes: false,
parse_order: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct Resources {
pub objects: Vec<Object>,
pub materials: Vec<super::Material>,
pub color_groups: Vec<super::ColorGroup>,
pub displacement_maps: Vec<super::Displacement2D>,
pub norm_vector_groups: Vec<super::NormVectorGroup>,
pub disp2d_groups: Vec<super::Disp2DGroup>,
pub slice_stacks: Vec<super::SliceStack>,
pub base_material_groups: Vec<super::BaseMaterialGroup>,
pub texture2d_resources: Vec<super::Texture2D>,
pub texture2d_groups: Vec<super::Texture2DGroup>,
pub composite_materials: Vec<super::CompositeMaterials>,
pub multi_properties: Vec<super::MultiProperties>,
pub volumetric_data: Vec<super::VolumetricData>,
pub volumetric_property_groups: Vec<super::VolumetricPropertyGroup>,
}
impl Resources {
pub fn new() -> Self {
Self {
objects: Vec::new(),
materials: Vec::new(),
color_groups: Vec::new(),
displacement_maps: Vec::new(),
norm_vector_groups: Vec::new(),
disp2d_groups: Vec::new(),
slice_stacks: Vec::new(),
base_material_groups: Vec::new(),
texture2d_resources: Vec::new(),
texture2d_groups: Vec::new(),
composite_materials: Vec::new(),
multi_properties: Vec::new(),
volumetric_data: Vec::new(),
volumetric_property_groups: Vec::new(),
}
}
}
impl Default for Resources {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct BuildItem {
pub objectid: usize,
pub transform: Option<[f64; 12]>,
pub production_uuid: Option<String>,
pub production_path: Option<String>,
}
impl BuildItem {
pub fn new(objectid: usize) -> Self {
Self {
objectid,
transform: None,
production_uuid: None,
production_path: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Build {
pub items: Vec<BuildItem>,
pub production_uuid: Option<String>,
}
impl Build {
pub fn new() -> Self {
Self {
items: Vec::new(),
production_uuid: None,
}
}
}
impl Default for Build {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetadataEntry {
pub name: String,
pub value: String,
pub preserve: Option<bool>,
}
impl MetadataEntry {
pub fn new(name: String, value: String) -> Self {
Self {
name,
value,
preserve: None,
}
}
pub fn new_with_preserve(name: String, value: String, preserve: bool) -> Self {
Self {
name,
value,
preserve: Some(preserve),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Thumbnail {
pub path: String,
pub content_type: String,
}
impl Thumbnail {
pub fn new(path: String, content_type: String) -> Self {
Self { path, content_type }
}
}
#[derive(Debug, Clone)]
pub struct Model {
pub unit: String,
pub xmlns: String,
pub required_extensions: Vec<Extension>,
pub required_custom_extensions: Vec<String>,
pub metadata: Vec<MetadataEntry>,
pub thumbnail: Option<Thumbnail>,
pub resources: Resources,
pub build: Build,
pub secure_content: Option<super::SecureContentInfo>,
}
impl Model {
pub fn new() -> Self {
Self {
unit: "millimeter".to_string(),
xmlns: "http://schemas.microsoft.com/3dmanufacturing/core/2015/02".to_string(),
required_extensions: Vec::new(),
required_custom_extensions: Vec::new(),
metadata: Vec::new(),
thumbnail: None,
resources: Resources::new(),
build: Build::new(),
secure_content: None,
}
}
pub fn get_metadata(&self, name: &str) -> Option<&str> {
self.metadata
.iter()
.find(|entry| entry.name == name)
.map(|entry| entry.value.as_str())
}
pub fn has_metadata(&self, name: &str) -> bool {
self.metadata.iter().any(|entry| entry.name == name)
}
}
impl Default for Model {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extensions::MaterialExtensionHandler;
#[test]
fn test_parser_config_new_has_empty_registry() {
let config = ParserConfig::new();
assert_eq!(config.registry().handlers().len(), 0);
}
#[test]
fn test_parser_config_with_all_extensions_has_default_registry() {
let config = ParserConfig::with_all_extensions();
assert_eq!(config.registry().handlers().len(), 8);
assert!(config.registry().get_handler(Extension::Material).is_some());
assert!(
config
.registry()
.get_handler(Extension::Production)
.is_some()
);
assert!(
config
.registry()
.get_handler(Extension::BeamLattice)
.is_some()
);
assert!(config.registry().get_handler(Extension::Slice).is_some());
assert!(
config
.registry()
.get_handler(Extension::BooleanOperations)
.is_some()
);
assert!(
config
.registry()
.get_handler(Extension::Displacement)
.is_some()
);
assert!(
config
.registry()
.get_handler(Extension::SecureContent)
.is_some()
);
}
#[test]
fn test_parser_config_with_extension_handler() {
let config = ParserConfig::new().with_extension_handler(Arc::new(MaterialExtensionHandler));
assert_eq!(config.registry().handlers().len(), 1);
assert!(config.registry().get_handler(Extension::Material).is_some());
}
#[test]
fn test_parser_config_registry_mut() {
let mut config = ParserConfig::new();
assert_eq!(config.registry().handlers().len(), 0);
config
.registry_mut()
.register(Arc::new(MaterialExtensionHandler));
assert_eq!(config.registry().handlers().len(), 1);
assert!(config.registry().get_handler(Extension::Material).is_some());
}
#[test]
fn test_parser_config_clone() {
let config1 =
ParserConfig::new().with_extension_handler(Arc::new(MaterialExtensionHandler));
let config2 = config1.clone();
assert_eq!(
config1.registry().handlers().len(),
config2.registry().handlers().len()
);
assert_eq!(config1.registry().handlers().len(), 1);
assert!(
config2
.registry()
.get_handler(Extension::Material)
.is_some()
);
}
#[test]
fn test_parser_config_chaining() {
let config = ParserConfig::new()
.with_extension(Extension::Material)
.with_extension_handler(Arc::new(MaterialExtensionHandler));
assert!(config.supports(&Extension::Material));
assert_eq!(config.registry().handlers().len(), 2);
}
#[test]
fn test_extension_from_namespace_beamlattice() {
assert_eq!(
Extension::from_namespace(
"http://schemas.microsoft.com/3dmanufacturing/beamlattice/2017/02"
),
Some(Extension::BeamLattice)
);
}
#[test]
fn test_extension_from_namespace_beamlattice_balls() {
assert_eq!(
Extension::from_namespace(
"http://schemas.microsoft.com/3dmanufacturing/beamlattice/balls/2020/07"
),
Some(Extension::BeamLattice)
);
}
#[test]
fn test_extension_from_namespace_securecontent_variants() {
assert_eq!(
Extension::from_namespace(
"http://schemas.microsoft.com/3dmanufacturing/securecontent/2019/07"
),
Some(Extension::SecureContent)
);
assert_eq!(
Extension::from_namespace(
"http://schemas.microsoft.com/3dmanufacturing/securecontent/2019/04"
),
Some(Extension::SecureContent)
);
}
#[test]
fn test_extension_from_namespace_displacement_variants() {
assert_eq!(
Extension::from_namespace(
"http://schemas.microsoft.com/3dmanufacturing/displacement/2022/07"
),
Some(Extension::Displacement)
);
assert_eq!(
Extension::from_namespace("http://schemas.3mf.io/3dmanufacturing/displacement/2023/10"),
Some(Extension::Displacement)
);
}
}