use std::collections::BTreeMap;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize};
use crate::appearance::{Appearance, AppearanceBinding};
use crate::attributes::SourceAttribute;
use crate::drawings::Drawing;
use crate::features::{
DesignConfiguration, DesignParameter, Feature, FeatureInputTopology, FeatureResultTopology,
};
use crate::geometry::{Curve, Pcurve, ProceduralCurve, ProceduralSurface, Surface};
use crate::native::Native;
use crate::presentation::{PresentationDocument, ViewPresentation};
use crate::products::{AssemblyJoint, Occurrence, ProductDefinition};
use crate::semantic_annotations::SemanticAnnotation;
use crate::sketches::{
Sketch, SketchConstraint, SketchEntity, SpatialSketch, SpatialSketchConstraint,
SpatialSketchEntity,
};
use crate::spreadsheets::Spreadsheet;
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::{NativeUnknownRecord, UnknownRecord};
macro_rules! arena_registry {
($macro:ident) => {
$macro! {
bodies: Body, "Body arena.", [];
regions: Region, "Region arena.", [];
shells: Shell, "Shell arena.", [];
faces: Face, "Face arena.", [];
loops: Loop, "Loop arena.", [];
coedges: Coedge, "Coedge arena.", [];
edges: Edge, "Edge arena.", [];
vertices: Vertex, "Vertex arena.", [];
points: Point, "Point arena.", [];
surfaces: Surface, "Surface arena.", [];
curves: Curve, "Curve arena.", [];
subds: SubdSurface, "Subdivision surface arena.", [];
pcurves: Pcurve, "Pcurve arena.", [];
procedural_surfaces: ProceduralSurface, "Procedural surface arena.", [];
procedural_curves: ProceduralCurve, "Procedural curve arena.", [];
assets: crate::assets::Asset, "Embedded and externally referenced document resources.", [serde(default, skip_serializing_if = "Vec::is_empty")];
features: Feature, "Feature arena.", [];
feature_input_topologies: FeatureInputTopology, "Feature input-topology arena.", [serde(default, skip_serializing_if = "Vec::is_empty")];
feature_result_topologies: FeatureResultTopology, "Feature result-topology arena.", [serde(default, skip_serializing_if = "Vec::is_empty")];
configurations: DesignConfiguration, "Design configuration arena.", [serde(default)];
parameters: DesignParameter, "Design parameter arena.", [serde(default)];
sketches: Sketch, "Planar sketch arena.", [serde(default)];
sketch_entities: SketchEntity, "Solved sketch entity arena.", [serde(default)];
sketch_constraints: SketchConstraint, "Sketch constraint arena.", [serde(default)];
spatial_sketches: SpatialSketch, "Spatial sketch arena.", [serde(default)];
spatial_sketch_entities: SpatialSketchEntity, "Solved spatial sketch entity arena.", [serde(default)];
spatial_sketch_constraints: SpatialSketchConstraint, "Spatial sketch constraint arena.", [serde(default, skip_serializing_if = "Vec::is_empty")];
spreadsheets: Spreadsheet, "Spreadsheet arena.", [serde(default)];
product_definitions: ProductDefinition, "Product definition arena.", [serde(default)];
occurrences: Occurrence, "Product occurrence arena.", [serde(default)];
assembly_joints: AssemblyJoint, "Assembly joint arena.", [serde(default)];
drawings: Drawing, "Drawing page, resource, view, and annotation arena.", [serde(default)];
semantic_annotations: SemanticAnnotation, "Semantic dimension, note, symbol, and callout arena.", [serde(default)];
presentation_documents: PresentationDocument, "Document presentation arena.", [serde(default)];
view_presentations: ViewPresentation, "View-provider presentation arena.", [serde(default)];
tessellations: Tessellation, "Tessellation arena.", [];
appearances: Appearance, "Appearance arena.", [];
appearance_bindings: AppearanceBinding, "Appearance binding arena.", [];
attributes: SourceAttribute, "Attribute arena.", [];
pmi: crate::pmi::PmiAnnotation, "Product-manufacturing information arena.", [serde(default)];
presentation_layers: crate::presentation::PresentationLayer, "Presentation layer arena.", [serde(default)];
}
};
}
pub(crate) use arena_registry;
macro_rules! declare_model {
($($field:ident: $ty:ty, $doc:literal, [$($attribute:meta),*];)*) => {
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct Model {
$(
$(#[$attribute])*
#[doc = $doc]
pub $field: Vec<$ty>,
)*
}
impl Model {
pub fn arena_names() -> &'static [&'static str] {
&[$(stringify!($field)),*]
}
pub(crate) fn identity_at(
&self,
kind: crate::schema::EntityKind,
index: usize,
) -> Option<&str> {
$(if kind == <$ty as crate::schema::EntitySchema>::KIND {
return self
.$field
.get(index)
.map(crate::schema::EntitySchema::identity);
})*
None
}
pub fn entity_count(&self) -> usize {
0 $(+ self.$field.len())*
}
pub fn visit_references(
&self,
visitor: &mut dyn FnMut(crate::schema::Reference),
) {
$(for entity in &self.$field {
crate::schema::EntitySchema::visit_references(entity, visitor);
})*
}
pub fn finalize(&mut self) {
$(self.$field.sort_by(|left, right| {
crate::schema::EntitySchema::identity(left)
.cmp(crate::schema::EntitySchema::identity(right))
});)*
}
pub fn extend_rewritten<R: EntityRewrite>(
&mut self,
other: Self,
rewrite: &mut R,
) -> Result<(), R::Error> {
$(
self.$field.reserve(other.$field.len());
for entity in other.$field {
self.$field.push(rewrite.rewrite(entity)?);
}
)*
Ok(())
}
}
};
}
macro_rules! assert_entity_schemas {
($($field:ident: $ty:ty, $doc:literal, [$($attribute:meta),*];)*) => {
const _: fn() = || {
fn assert_schema<T: crate::schema::EntitySchema>() {}
$(assert_schema::<$ty>();)*
};
};
}
pub trait EntityRewrite {
type Error;
fn rewrite<T: Serialize + DeserializeOwned>(&mut self, entity: T) -> Result<T, Self::Error>;
}
macro_rules! declare_model_view {
($($field:ident: $ty:ty, $doc:literal, [$($attribute:meta),*];)*) => {
#[derive(Serialize)]
pub(crate) struct SortedModel<'a> {
$($(#[$attribute])* $field: Vec<&'a $ty>,)*
}
impl Model {
pub(crate) fn sorted(&self) -> SortedModel<'_> {
SortedModel {
$($field: sorted_refs(&self.$field),)*
}
}
}
};
}
fn sorted_refs<T: crate::schema::EntitySchema>(entities: &[T]) -> Vec<&T> {
let mut refs = entities.iter().collect::<Vec<_>>();
refs.sort_by(|left, right| left.identity().cmp(right.identity()));
refs
}
pub const IR_VERSION: &str = "5";
arena_registry!(declare_model);
arena_registry!(assert_entity_schemas);
arena_registry!(declare_model_view);
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)
}
#[cfg(feature = "schema")]
fn ir_version_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"const": IR_VERSION
})
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct CadIr {
#[serde(deserialize_with = "deserialize_ir_version")]
#[cfg_attr(feature = "schema", schemars(schema_with = "ir_version_schema"))]
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 native: Native,
}
impl CadIr {
pub fn native_unknowns(
&self,
format: &str,
) -> Result<Vec<NativeUnknownRecord>, crate::native::NativeConvertError> {
self.native_unknowns_iter(format).collect()
}
pub fn native_unknowns_iter<'a>(
&'a self,
format: &str,
) -> impl Iterator<Item = Result<NativeUnknownRecord, crate::native::NativeConvertError>> + 'a
{
self.native
.namespace(format)
.into_iter()
.flat_map(|namespace| namespace.arena_iter_as("unknowns"))
}
pub fn all_native_unknowns_iter(
&self,
) -> impl Iterator<Item = Result<NativeUnknownRecord, crate::native::NativeConvertError>> + '_
{
self.native
.0
.values()
.flat_map(|namespace| namespace.arena_iter_as("unknowns"))
}
pub fn set_native_unknowns(
&mut self,
format: &str,
records: &[NativeUnknownRecord],
) -> Result<(), crate::native::NativeConvertError> {
self.set_native_unknowns_from(format, records.iter())
}
pub fn set_native_unknowns_from<T: Serialize, I: IntoIterator<Item = T>>(
&mut self,
format: &str,
records: I,
) -> Result<(), crate::native::NativeConvertError> {
self.unknowns_namespace_mut(format)
.set_arena_from("unknowns", records)
}
pub fn set_native_unknowns_owned(&mut self, format: &str, records: Vec<UnknownRecord>) {
let namespace = self.unknowns_namespace_mut(format);
let mut converted = records
.into_iter()
.map(UnknownRecord::into_native_record)
.collect::<Vec<_>>();
converted.sort_by(|left, right| left.id().cmp(right.id()));
namespace.arenas.insert("unknowns".into(), converted);
}
fn unknowns_namespace_mut(&mut self, format: &str) -> &mut crate::native::NativeNamespace {
let namespace = self.native.namespace_mut(format);
if namespace.version == 0 {
namespace.version = 1;
}
namespace
}
pub fn empty(units: Units) -> Self {
Self {
ir_version: IR_VERSION.to_owned(),
source: None,
units,
tolerances: Tolerances::default(),
model: Model::default(),
native: Native::default(),
}
}
pub fn ir_version(&self) -> &str {
&self.ir_version
}
pub fn to_canonical_json(&self) -> Result<String, serde_json::Error> {
let mut canonical = self.clone();
canonical.finalize();
serde_json::to_string_pretty(&canonical)
}
pub fn from_json(s: &str) -> Result<Self, serde_json::Error> {
#[derive(Deserialize)]
struct VersionProbe {
ir_version: Option<serde_json::Value>,
}
let probe = serde_json::from_str::<VersionProbe>(s)?;
let version = probe
.ir_version
.as_ref()
.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_str(s)
}
pub fn finalize(&mut self) {
self.model.finalize();
self.native.finalize();
}
pub fn census(&self) -> std::collections::BTreeMap<String, usize> {
entity_census(self)
}
#[cfg(test)]
pub(crate) fn set_ir_version_for_test(&mut self, version: impl Into<String>) {
self.ir_version = version.into();
}
}
macro_rules! define_registered_entity_census {
($( $field:ident: $element:ty, $doc:literal, [$($attribute:meta),*]; )*) => {
fn registered_entity_census(ir: &CadIr) -> BTreeMap<String, usize> {
BTreeMap::from([
$((stringify!($field).into(), ir.model.$field.len())),*
])
}
};
}
arena_registry!(define_registered_entity_census);
pub fn entity_census(ir: &CadIr) -> BTreeMap<String, usize> {
let mut counts = registered_entity_census(ir);
counts.insert(
"surfaces_unknown_geometry".into(),
ir.model
.surfaces
.iter()
.filter(|surface| {
matches!(
surface.geometry,
crate::geometry::SurfaceGeometry::Unknown { .. }
)
})
.count(),
);
for loss in ir.native.loss_counts() {
counts.insert(format!("native.{}.{}", loss.format, loss.kind), loss.count);
}
counts
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SourceMeta {
pub format: String,
#[serde(default)]
pub attributes: BTreeMap<String, String>,
}
#[cfg(test)]
mod tests;