use std::collections::HashMap;
use std::sync::LazyLock;
use std::fmt::{Display, Formatter, Result};
use crate::util::path::*;
use super::model::*;
use super::primitive::*;
use super::securecontent::KeyStore;
#[derive(Debug, Clone, Default)]
pub struct Package {
pub model: Model,
pub models: HashMap<String, Model>,
pub thumbnail: Option<Attachment>,
pub attachments: HashMap<String, Attachment>,
pub content_types: ContentTypes,
pub relationships: Relationships,
pub relative_rels: HashMap<String, Relationships>,
pub key_store: Option<KeyStore>,
}
#[derive(Debug, Clone)]
pub struct Attachment {
pub data: Vec<u8>,
pub extension: String,
pub content_type: String,
}
impl Attachment {
pub fn png(data: Vec<u8>) -> Self {
Self {
data,
extension: Extension::PNG.to_string(),
content_type: ContentType::PNG.to_string(),
}
}
pub fn jpg(data: Vec<u8>) -> Self {
Self {
data,
extension: Extension::JPG.to_string(),
content_type: ContentType::JPG.to_string(),
}
}
pub fn jpeg(data: Vec<u8>) -> Self {
Self {
data,
extension: Extension::JPEG.to_string(),
content_type: ContentType::JPEG.to_string(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ContentTypes {
pub defaults: Vec<ContentTypeDefault>,
pub overrides: Vec<ContentTypeOverride>,
pub unknowns: HashMap<String, Vec<HashMap<String, String>>>,
}
impl ContentTypes {
pub fn ensure_default(&mut self, extension: &str, content_type: &str) {
if !self.defaults.iter().any(|d| d.extension == extension.to_string()) {
self.defaults.push(ContentTypeDefault {
extension: extension.to_string(),
content_type: content_type.to_string(),
});
}
}
pub fn ensure_override(&mut self, part_name: &str, content_type: &str) {
if !self.overrides.iter().any(|o| o.part_name == part_name.to_string()) {
self.overrides.push(ContentTypeOverride {
part_name: part_name.to_string(),
content_type: content_type.to_string(),
});
}
}
}
#[derive(Debug, Clone)]
pub struct ContentTypeDefault {
pub extension: String,
pub content_type: String,
}
#[derive(Debug, Clone)]
pub struct ContentTypeOverride {
pub part_name: String,
pub content_type: String,
}
#[derive(Debug, Clone, Default)]
pub struct Relationships {
pub relationships: Vec<Relationship>,
}
impl Relationships {
pub fn add_relationship(&mut self, relationship: Relationship) {
self.relationships.push(relationship);
}
pub fn next_id(&self) -> String {
"rel".to_owned() + &self.relationships.len().to_string()
}
pub fn is_empty(&self) -> bool {
self.relationships.is_empty()
}
pub fn ensure(&mut self, r#type: &str, target: &str) {
if !self.relationships.iter().any(|r| r.r#type == r#type.to_string() && r.target == target.to_string()) {
self.relationships.push(Relationship {
id: "rel".to_owned() + &self.relationships.len().to_string(),
r#type: r#type.to_string(),
target: target.to_string(),
});
}
}
}
#[derive(Debug, Clone)]
pub struct Relationship {
pub id: String,
pub r#type: String,
pub target: String,
}
impl Package {
pub fn new() -> Self {
Self {
model: Model::new(),
models: HashMap::new(),
thumbnail: None,
attachments: HashMap::new(),
content_types: ContentTypes::default(),
relationships: Relationships::default(),
relative_rels: HashMap::new(),
key_store: None,
}
}
pub fn with_model(model: Model) -> Self {
Self {
model: model.clone(),
models: HashMap::new(),
thumbnail: None,
attachments: HashMap::new(),
content_types: ContentTypes::default(),
relationships: Relationships::default(),
relative_rels: HashMap::new(),
key_store: None,
}
}
pub fn set_model(&mut self, model: Model) -> &mut Self {
self.model = model;
self
}
pub fn add_model(&mut self, path: impl Into<String>, model: Model) -> &mut Self {
self.models.insert(path.into(), model);
self
}
pub fn set_content_types(&mut self, content_types: ContentTypes) {
self.content_types = content_types;
}
pub fn set_relationships(&mut self, relationships: Relationships) {
self.relationships = relationships;
}
pub fn add_relationships(&mut self, path: impl Into<String>, relationships: Relationships) {
self.relative_rels.insert(path.into(), relationships);
}
pub fn add_attachment(&mut self, path: impl Into<String>, data: Vec<u8>) {
let path = path.into();
let path = path.as_str();
let extension = extension(path);
let path = absolute(path, None);
let content_type = CTYPE_EXT_MAP[&extension].to_string();
self.attachments.insert(
path,
Attachment {
data,
extension,
content_type,
}
);
}
pub fn get_attachment(&self, path: &str) -> Option<&[u8]> {
let path = if path.starts_with('/') {
path.to_string()
} else {
format!("/{}", path)
};
self.attachments.get(&path).map(|v| v.data.as_slice())
}
pub fn set_key_store(&mut self, key_store: KeyStore) -> &mut Self {
self.key_store = Some(key_store);
self
}
pub fn get_key_store(&self) -> Option<&KeyStore> {
self.key_store.as_ref()
}
pub fn set_title(&mut self, title: impl Into<String>) -> &mut Self {
self.model.set_title(title);
self
}
pub fn set_designer(&mut self, designer: impl Into<String>) -> &mut Self {
self.model.set_designer(designer);
self
}
pub fn set_description(&mut self, description: impl Into<String>) -> &mut Self {
self.model.set_description(description);
self
}
pub fn set_application(&mut self, application: impl Into<String>) -> &mut Self {
self.model.set_application(application);
self
}
pub fn set_thumbnail(&mut self, thumbnail: Attachment) {
self.thumbnail = Some(thumbnail);
}
pub fn set_thumbnail_png(&mut self, data: Vec<u8>) -> &mut Self {
self.set_thumbnail(Attachment::png(data));
self
}
pub fn set_thumbnail_jpg(&mut self, data: Vec<u8>) -> &mut Self {
self.set_thumbnail(Attachment::jpg(data));
self
}
pub fn set_thumbnail_jpeg(&mut self, data: Vec<u8>) -> &mut Self {
self.set_thumbnail(Attachment::jpeg(data));
self
}
pub fn set_unit(&mut self, unit: Unit) -> &mut Self {
self.model.unit = unit;
self
}
pub fn add_cube(mut self, size: f64) -> Self {
let mesh = Mesh::cube(size);
let mut object = self.model.gen_object();
object.set_name("Cube").set_mesh(mesh);
let id = self.model.add_object(object);
self.model.add_build_item(id, None);
self
}
pub fn add_cube_placed(mut self, size: f64, x: f64, y: f64, z: f64) -> Self {
let mesh = Mesh::cube(size);
let mut object = self.model.gen_object();
object.set_name("Cube").set_mesh(mesh);
let id = self.model.add_object(object);
let transform = Matrix3D::translation(x, y, z);
self.model.add_build_item(id, Some(transform));
self
}
pub fn add_cube_colored(mut self, size: f64, color: Color) -> Self {
let material_id = self.model.add_base_materials(vec![BaseMaterial::new("Color", color)]);
let mesh = Mesh::cube(size);
let mut object = self.model.gen_object();
object.set_name("Cube").set_mesh(mesh).set_material(material_id, 0);
let id = self.model.add_object(object);
self.model.add_build_item(id, None);
self
}
pub fn add_mesh(mut self, mesh: Mesh) -> Self {
let mut object = self.model.gen_object();
object.set_mesh(mesh);
let id = self.model.add_object(object);
self.model.add_build_item(id, None);
self
}
pub fn add_mesh_named(mut self, name: impl Into<String>, mesh: Mesh) -> Self {
let mut object = self.model.gen_object();
object.set_name(name).set_mesh(mesh);
let id = self.model.add_object(object);
self.model.add_build_item(id, None);
self
}
}
pub static EXT_CTYPE_MAP: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
let mut map = HashMap::new();
map.insert(Extension::JPEG_EXT.to_string(), ContentType::JPEG_CT.to_string());
map.insert(Extension::JPG_EXT.to_string(), ContentType::JPG_CT.to_string());
map.insert(Extension::PNG_EXT.to_string(), ContentType::PNG_CT.to_string());
map.insert(Extension::RELATIONSHIP_EXT.to_string(), ContentType::RELATIONSHIP_CT.to_string());
map.insert(Extension::MODEL_EXT.to_string(), ContentType::MODEL_CT.to_string());
map.insert(Extension::TEXTURE_EXT.to_string(), ContentType::TEXTURE_CT.to_string());
map
});
pub static CTYPE_EXT_MAP: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
let mut map = HashMap::new();
map.insert(ContentType::JPEG_CT.to_string(), Extension::JPEG_EXT.to_string());
map.insert(ContentType::JPG_CT.to_string(), Extension::JPG_EXT.to_string());
map.insert(ContentType::PNG_CT.to_string(), Extension::PNG_EXT.to_string());
map.insert(ContentType::RELATIONSHIP_CT.to_string(), Extension::RELATIONSHIP_EXT.to_string());
map.insert(ContentType::MODEL_CT.to_string(), Extension::MODEL_EXT.to_string());
map.insert(ContentType::TEXTURE_CT.to_string(), Extension::TEXTURE_EXT.to_string());
map
});
pub enum Pathway {
Model,
Thumbnail,
ThumbnailJpg,
ThumbnailPng,
ThumbnailJpeg,
ContentTypes,
RelationshipsRoot,
RelationshipsGlob,
}
impl Pathway {
pub const MODEL_PATH: &str = "/3D/3dmodel.model";
pub const THUMBNAIL_PATH: &str = "/Metadata/thumbnail";
pub const THUMBNAIL_JPG_PATH: &str = "/Metadata/thumbnail.jpg";
pub const THUMBNAIL_PNG_PATH: &str = "/Metadata/thumbnail.png";
pub const THUMBNAIL_JPEG_PATH: &str = "/Metadata/thumbnail.jpeg";
pub const CONTENT_TYPES_PATH: &str = "/[Content_Types].xml";
pub const RELS_ROOT_PATH: &str = "/_rels/.rels";
pub const RELS_GLOB_EXT: &str = "/**/*/_rels/*.rels";
pub const MODEL_GLOB_EXT: &str = "/3D/**/.*.model";
pub const KEY_STORE_PATH: &str = "/Secure/keystore.xml";
}
impl Display for Pathway {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::Model => write!(f, "{}", Self::MODEL_PATH),
Self::Thumbnail => write!(f, "{}", Self::THUMBNAIL_PATH),
Self::ThumbnailJpg => write!(f, "{}", Self::THUMBNAIL_JPG_PATH),
Self::ThumbnailPng => write!(f, "{}", Self::THUMBNAIL_PNG_PATH),
Self::ThumbnailJpeg => write!(f, "{}", Self::THUMBNAIL_JPEG_PATH),
Self::ContentTypes => write!(f, "{}", Self::CONTENT_TYPES_PATH),
Self::RelationshipsRoot => write!(f, "{}", Self::RELS_ROOT_PATH),
Self::RelationshipsGlob => write!(f, "{}", Self::RELS_GLOB_EXT),
}
}
}
pub enum Extension {
JPEG,
JPG,
PNG,
Relationship,
Model,
Texture,
}
impl Extension {
pub const JPEG_EXT: &str = "jpeg";
pub const JPG_EXT: &str = "jpg";
pub const PNG_EXT: &str = "png";
pub const RELATIONSHIP_EXT: &str = "rels";
pub const MODEL_EXT: &str = "model";
pub const TEXTURE_EXT: &str = "texture";
pub const XML_EXT: &str = "xml";
}
impl Display for Extension {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::JPEG => write!(f, "{}", Self::JPEG_EXT),
Self::JPG => write!(f, "{}", Self::JPG_EXT),
Self::PNG => write!(f, "{}", Self::PNG_EXT),
Self::Relationship => write!(f, "{}", Self::RELATIONSHIP_EXT),
Self::Model => write!(f, "{}", Self::MODEL_EXT),
Self::Texture => write!(f, "{}", Self::TEXTURE_EXT),
}
}
}
pub enum ContentType {
JPEG,
JPG,
PNG,
Relationship,
Model,
PrintTicket,
Texture,
}
impl ContentType {
pub const JPEG_CT: &str = "image/jpeg";
pub const JPG_CT: &str = "image/jpeg";
pub const PNG_CT: &str = "image/png";
pub const RELATIONSHIP_CT: &str = "application/vnd.openxmlformats-package.relationships+xml";
pub const MODEL_CT: &str = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml";
pub const PRINT_TICKET_CT: &str = "application/vnd.ms-printing.printticket+xml";
pub const TEXTURE_CT: &str = "application/vnd.ms-package.3dmanufacturing-3dmodeltexture";
pub const KEY_STORE_CT: &str = "application/vnd.ms-package.3dmanufacturing-keystore+xml";
}
impl Display for ContentType {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::JPEG => write!(f, "{}", Self::JPEG_CT),
Self::JPG => write!(f, "{}", Self::JPG_CT),
Self::PNG => write!(f, "{}", Self::PNG_CT),
Self::Relationship => write!(f, "{}", Self::RELATIONSHIP_CT),
Self::Model => write!(f, "{}", Self::MODEL_CT),
Self::PrintTicket => write!(f, "{}", Self::PRINT_TICKET_CT),
Self::Texture => write!(f, "{}", Self::TEXTURE_CT),
}
}
}
pub enum Namespace {
Core,
TriangleSets,
Model,
Thumbnail,
Relationship,
}
impl Namespace {
pub const CORE_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/core/2015/02";
pub const CONTENT_TYPES_NS: &str = "http://schemas.openxmlformats.org/package/2006/content-types";
pub const MODEL_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel";
pub const THUMBNAIL_NS: &str = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail";
pub const RELATIONSHIP_NS: &str = "http://schemas.openxmlformats.org/package/2006/relationships";
pub const PRINT_TICKET_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/2013/01/printticket";
pub const MUST_PRESERVE_NS: &str = "http://schemas.openxmlformats.org/package/2006/relationships/mustpreserve";
pub const TRIANGLESETS_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/trianglesets/2021/07";
pub const BEAMLATTICE_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/beamlattice/2017/02";
pub const BALLS_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/beamlattice/balls/2020/07";
pub const DISPLACEMENT_NS: &str = "http://schemas.3mf.io/3dmanufacturing/displacement/2023/10";
pub const MATERIALS_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/material/2015/02";
pub const PRODUCTION_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/production/2015/06";
pub const ALTERNATIVES_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/production/alternatives/2021/04";
pub const SECURECONTENT_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/securecontent/2019/04";
pub const KEY_STORE_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/2019/04/keystore";
pub const ENCRYPTED_FILE_NS: &str = "http://schemas.openxmlformats.org/package/2006/relationships/encryptedfile";
pub const SLICE_NS: &str = "http://schemas.microsoft.com/3dmanufacturing/slice/2015/07";
pub const VOLUMETRIC_NS: &str = "http://schemas.3mf.io/3dmanufacturing/volumetric/2022/01";
pub const IMPLICIT_NS: &str = "http://schemas.3mf.io/3dmanufacturing/implicit/2023/12";
}
impl Display for Namespace {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::Core => write!(f, "{}", Self::CORE_NS),
Self::TriangleSets => write!(f, "{}", Self::TRIANGLESETS_NS),
Self::Model => write!(f, "{}", Self::MODEL_NS),
Self::Thumbnail => write!(f, "{}", Self::THUMBNAIL_NS),
Self::Relationship => write!(f, "{}", Self::RELATIONSHIP_NS),
}
}
}