use std::collections::BTreeMap;
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize};
use crate::annotations::Annotations;
use crate::appearance::{Appearance, AppearanceBinding};
use crate::attributes::SourceAttribute;
use crate::features::Feature;
use crate::geometry::{Curve, Pcurve, ProceduralCurve, ProceduralSurface, Surface};
use crate::native::Native;
use crate::subd::SubdSurface;
use crate::tessellation::Tessellation;
use crate::topology::{Body, Coedge, Edge, Face, Loop, Point, Region, Shell, Vertex};
use crate::units::{Tolerances, Units};
use crate::unknown::UnknownRecord;
macro_rules! arena_registry {
($macro:ident) => {
$macro! {
bodies: Body, "Body arena.", [] => |e| e.id.0.clone();
regions: Region, "Region arena.", [] => |e| e.id.0.clone();
shells: Shell, "Shell arena.", [] => |e| e.id.0.clone();
faces: Face, "Face arena.", [] => |e| e.id.0.clone();
loops: Loop, "Loop arena.", [] => |e| e.id.0.clone();
coedges: Coedge, "Coedge arena.", [] => |e| e.id.0.clone();
edges: Edge, "Edge arena.", [] => |e| e.id.0.clone();
vertices: Vertex, "Vertex arena.", [] => |e| e.id.0.clone();
points: Point, "Point arena.", [] => |e| e.id.0.clone();
surfaces: Surface, "Surface arena.", [] => |e| e.id.0.clone();
curves: Curve, "Curve arena.", [] => |e| e.id.0.clone();
subds: SubdSurface, "Subdivision surface arena.", [] => |e| e.id.0.clone();
pcurves: Pcurve, "Pcurve arena.", [] => |e| e.id.0.clone();
procedural_surfaces: ProceduralSurface, "Procedural surface arena.", [] => |e| e.id.0.clone();
procedural_curves: ProceduralCurve, "Procedural curve arena.", [] => |e| e.id.0.clone();
features: Feature, "Feature arena.", [] => |e| e.id.0.clone();
tessellations: Tessellation, "Tessellation arena.", [] => |e| e.id.clone();
appearances: Appearance, "Appearance arena.", [] => |e| e.id.0.clone();
appearance_bindings: AppearanceBinding, "Appearance binding arena.", [] => |e| e.id.clone();
attributes: SourceAttribute, "Attribute arena.", [] => |e| e.id.0.clone();
}
};
}
pub(crate) use arena_registry;
macro_rules! declare_model {
($($field:ident: $ty:ty, $doc:literal, $attrs:tt => $key:expr;)*) => {
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Model {
$(
#[doc = $doc]
pub $field: Vec<$ty>,
)*
}
impl Model {
pub fn arena_names() -> &'static [&'static str] {
&[$(stringify!($field)),*]
}
pub fn finalize(&mut self) {
$(self.$field.sort_by_key($key);)*
}
}
};
}
pub const IR_VERSION: &str = "3";
arena_registry!(declare_model);
fn deserialize_ir_version<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let version = String::deserialize(deserializer)?;
if version != IR_VERSION {
return Err(serde::de::Error::custom(format!(
"unsupported ir_version {version:?}; expected {IR_VERSION}"
)));
}
Ok(version)
}
fn ir_version_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"const": IR_VERSION
})
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CadIr {
#[serde(deserialize_with = "deserialize_ir_version")]
#[schemars(schema_with = "ir_version_schema")]
pub ir_version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<SourceMeta>,
pub units: Units,
pub tolerances: Tolerances,
pub model: Model,
#[serde(default)]
pub annotations: Annotations,
#[serde(default)]
pub native: Native,
}
impl CadIr {
pub fn native_unknowns(
&self,
format: &str,
) -> Result<Vec<UnknownRecord>, crate::native::NativeConvertError> {
self.native.namespace(format).map_or_else(
|| Ok(Vec::new()),
|namespace| namespace.arena_as("unknowns"),
)
}
pub fn all_native_unknowns(
&self,
) -> Result<Vec<UnknownRecord>, crate::native::NativeConvertError> {
self.native
.0
.values()
.filter(|namespace| namespace.arenas.contains_key("unknowns"))
.try_fold(Vec::new(), |mut records, namespace| {
records.extend(namespace.arena_as::<UnknownRecord>("unknowns")?);
Ok(records)
})
}
pub fn set_native_unknowns(
&mut self,
format: &str,
records: &[UnknownRecord],
) -> Result<(), crate::native::NativeConvertError> {
let namespace = self.native.namespace_mut(format);
if namespace.version == 0 {
namespace.version = 1;
}
namespace.set_arena("unknowns", records)
}
pub fn push_native_unknown(
&mut self,
format: &str,
record: UnknownRecord,
) -> Result<(), crate::native::NativeConvertError> {
let mut records = self.native_unknowns(format)?;
records.retain(|existing| existing.id != record.id);
records.push(record);
self.set_native_unknowns(format, &records)
}
pub fn empty(units: Units) -> Self {
Self {
ir_version: IR_VERSION.to_owned(),
source: None,
units,
tolerances: Tolerances::default(),
model: Model::default(),
annotations: Annotations::default(),
native: Native::default(),
}
}
pub fn to_canonical_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn from_json(s: &str) -> Result<Self, serde_json::Error> {
let value: serde_json::Value = serde_json::from_str(s)?;
let version = value.get("ir_version").and_then(serde_json::Value::as_str);
if version != Some(IR_VERSION) {
return Err(<serde_json::Error as serde::de::Error>::custom(format!(
"unsupported ir_version {version:?}; expected {IR_VERSION}"
)));
}
serde_json::from_value(value)
}
pub fn finalize(&mut self) {
self.model.finalize();
self.native.finalize();
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SourceMeta {
pub format: String,
#[serde(default)]
pub attributes: BTreeMap<String, String>,
}