use std::collections::{HashMap, HashSet};
use crate::ir::arena::Arena;
use crate::ir::assembly::{AssemblyTree, Product, Transform3d};
use crate::ir::error::ConvertError;
use crate::ir::geometry::Pcurve;
use crate::ir::id::{
Curve2dId, CurveId, Direction2dId, DirectionId, EdgeId, FaceId, Placement1dId, Placement2dId,
Placement3dId, Point2dId, PointId, ProductId, ShellId, SolidId, SurfaceId, VertexId, WireId,
};
use crate::ir::model::{
AngleUnit, GeometryPool, LengthUnit, SolidAngleUnit, StepModel, TopologyPool, UnitContext,
};
use crate::ir::topology::{Orientation, OrientedEdge};
use crate::parser::entity::{Attribute, EntityGraph, RawEntity, RawEntityPart};
mod assembly;
mod geometry;
mod header;
mod passes;
mod topology;
mod units;
#[cfg(test)]
mod tests;
#[derive(Debug)]
pub struct ConvertResult {
pub model: StepModel,
pub warnings: Vec<ConvertError>,
}
#[derive(Default)]
#[allow(clippy::zero_sized_map_values)]
pub struct ReaderContext {
pub(super) geometry: GeometryPool,
pub(super) topology: TopologyPool,
pub(super) units: Option<UnitContext>,
pub(super) pcurve_subtree_ids: HashSet<u64>,
pub(super) length_unit_map: HashMap<u64, LengthUnit>,
pub(super) angle_unit_map: HashMap<u64, AngleUnit>,
pub(super) solid_angle_unit_map: HashMap<u64, SolidAngleUnit>,
pub(super) length_uncertainty_map: HashMap<u64, f64>,
pub(super) point_map: HashMap<u64, PointId>,
pub(super) direction_map: HashMap<u64, DirectionId>,
pub(super) surface_map: HashMap<u64, SurfaceId>,
pub(super) curve_map: HashMap<u64, CurveId>,
pub(super) placement_map: HashMap<u64, Placement3dId>,
pub(super) vector_map: HashMap<u64, (DirectionId, f64)>,
pub(super) axis1_map: HashMap<u64, Placement1dId>,
pub(super) point_2d_map: HashMap<u64, Point2dId>,
pub(super) direction_2d_map: HashMap<u64, Direction2dId>,
pub(super) curve_2d_map: HashMap<u64, Curve2dId>,
pub(super) vector_2d_map: HashMap<u64, (Direction2dId, f64)>,
pub(super) placement_2d_map: HashMap<u64, Placement2dId>,
pub(super) surface_curve_pcurves_map: HashMap<u64, Vec<Pcurve>>,
pub(super) vertex_map: HashMap<u64, VertexId>,
pub(super) edge_map: HashMap<u64, EdgeId>,
pub(super) face_bound_map: HashMap<u64, WireId>,
pub(super) face_map: HashMap<u64, FaceId>,
pub(super) shell_map: HashMap<u64, ShellId>,
pub(super) solid_map: HashMap<u64, SolidId>,
pub(super) oriented_edge_map: HashMap<u64, OrientedEdge>,
pub(super) edge_loop_map: HashMap<u64, Vec<OrientedEdge>>,
pub(super) vertex_loop_map: HashMap<u64, VertexId>,
pub(super) oriented_closed_shell_map: HashMap<u64, (ShellId, Orientation)>,
pub(super) assembly: Option<AssemblyTree>,
pub(super) assembly_products: Arena<Product>,
pub(super) product_arena_map: HashMap<u64, ProductId>,
pub(super) formation_to_product: HashMap<u64, u64>,
pub(super) pdef_to_product: HashMap<u64, u64>,
pub(super) absr_solid_map: HashMap<u64, SolidId>,
pub(super) absr_ref_frame_map: HashMap<u64, Placement3dId>,
pub(super) sbsm_shells_map: HashMap<u64, Vec<ShellId>>,
pub(super) mssr_shells_map: HashMap<u64, Vec<ShellId>>,
pub(super) mssr_ref_frame_map: HashMap<u64, Placement3dId>,
pub(super) srr_equiv_map: HashMap<u64, u64>,
pub(super) pdef_shape_to_pdef: HashMap<u64, u64>,
pub(super) pdef_shape_to_nauo: HashMap<u64, u64>,
pub(super) transform_map: HashMap<u64, Transform3d>,
pub(super) nauo_transform_map: HashMap<u64, Transform3d>,
pub(super) warnings: Vec<ConvertError>,
}
impl ReaderContext {
#[must_use]
pub fn convert(graph: &EntityGraph) -> ConvertResult {
let mut ctx = Self {
pcurve_subtree_ids: collect_pcurve_subtree_ids(graph),
..Self::default()
};
ctx.run_unit_pass(graph);
ctx.run_geometry_passes(graph);
ctx.run_topology_passes(graph);
ctx.run_assembly_passes(graph);
ctx.finalize_assembly();
let header = header::extract_file_header(&graph.header, &mut ctx.warnings);
ConvertResult {
model: StepModel {
geometry: ctx.geometry,
topology: ctx.topology,
units: ctx.units,
assembly: ctx.assembly,
schema: graph.schema.clone(),
header,
},
warnings: ctx.warnings,
}
}
fn finalize_assembly(&mut self) {
if self.product_arena_map.is_empty() {
return;
}
let mut is_child: HashSet<ProductId> = HashSet::new();
for product in self.assembly_products.iter() {
if let crate::ir::assembly::ProductContent::Group(instances) = &product.content {
for inst in instances {
is_child.insert(inst.child);
}
}
}
#[allow(clippy::cast_possible_truncation)]
let roots: Vec<ProductId> = self
.assembly_products
.iter()
.enumerate()
.map(|(i, _)| ProductId(i as u32))
.filter(|pid| !is_child.contains(pid))
.collect();
let root = match roots.as_slice() {
[single] => Some(*single),
[] => {
self.warnings.push(ConvertError::UnexpectedEntityForm {
entity_id: 0,
detail: String::from(
"assembly has no root candidate (every product appears as an instance child)",
),
});
Some(ProductId(0))
}
[first, ..] => {
self.warnings.push(ConvertError::UnexpectedEntityForm {
entity_id: 0,
detail: format!(
"assembly has {} root candidates, using the first",
roots.len()
),
});
Some(*first)
}
};
let products = std::mem::take(&mut self.assembly_products);
self.assembly = Some(AssemblyTree { products, root });
}
pub(super) fn resolve_point(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<PointId, ConvertError> {
self.point_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_direction(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<DirectionId, ConvertError> {
self.direction_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_curve(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<CurveId, ConvertError> {
self.curve_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_surface(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<SurfaceId, ConvertError> {
self.surface_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_vertex(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<VertexId, ConvertError> {
self.vertex_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_edge(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<EdgeId, ConvertError> {
self.edge_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_face_bound(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<WireId, ConvertError> {
self.face_bound_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_face(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<FaceId, ConvertError> {
self.face_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_shell(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<ShellId, ConvertError> {
self.shell_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_product_by_pdef(
&self,
from: u64,
pdef_ref: u64,
field_name: &'static str,
) -> Result<ProductId, ConvertError> {
let product_step_id =
self.pdef_to_product
.get(&pdef_ref)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to: pdef_ref,
field_name,
})?;
self.product_arena_map.get(&product_step_id).copied().ok_or(
ConvertError::MissingReference {
from,
to: product_step_id,
field_name,
},
)
}
pub(super) fn resolve_placement(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<Placement3dId, ConvertError> {
self.placement_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_vector(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<(DirectionId, f64), ConvertError> {
self.vector_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_axis1(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<Placement1dId, ConvertError> {
self.axis1_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_oriented_edge(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<OrientedEdge, ConvertError> {
self.oriented_edge_map
.get(&to)
.copied()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
pub(super) fn resolve_edge_loop(
&self,
from: u64,
to: u64,
field_name: &'static str,
) -> Result<Vec<OrientedEdge>, ConvertError> {
self.edge_loop_map
.get(&to)
.cloned()
.ok_or(ConvertError::MissingReference {
from,
to,
field_name,
})
}
}
pub(super) fn bool_to_orientation(same_sense: bool) -> Orientation {
if same_sense {
Orientation::Forward
} else {
Orientation::Reversed
}
}
pub(super) fn find_part_attrs<'a>(
parts: &'a [RawEntityPart],
name: &str,
) -> Option<&'a [Attribute]> {
parts
.iter()
.find(|p| p.name == name)
.map(|p| p.attributes.as_slice())
}
pub(super) fn require_part_attrs<'a>(
parts: &'a [RawEntityPart],
name: &'static str,
entity_id: u64,
) -> Result<&'a [Attribute], ConvertError> {
find_part_attrs(parts, name).ok_or(ConvertError::UnexpectedEntityForm {
entity_id,
detail: format!("missing required part '{name}'"),
})
}
pub(super) fn has_all_parts(parts: &[RawEntityPart], required: &[&str]) -> bool {
required
.iter()
.all(|name| parts.iter().any(|p| p.name == *name))
}
pub(super) fn collect_pcurve_subtree_ids(graph: &EntityGraph) -> HashSet<u64> {
let mut ids = HashSet::new();
for (&id, entity) in &graph.entities {
if let RawEntity::Simple { name, .. } = entity
&& name == "DEFINITIONAL_REPRESENTATION"
{
collect_refs_transitive(id, graph, &mut ids);
}
}
ids
}
fn collect_refs_transitive(id: u64, graph: &EntityGraph, skip: &mut HashSet<u64>) {
if !skip.insert(id) {
return;
}
let Some(entity) = graph.get(id) else {
return;
};
match entity {
RawEntity::Simple { attributes, .. } => {
for attr in attributes {
walk_refs_in_attr(attr, graph, skip);
}
}
RawEntity::Complex { parts, .. } => {
for part in parts {
for attr in &part.attributes {
walk_refs_in_attr(attr, graph, skip);
}
}
}
}
}
fn walk_refs_in_attr(attr: &Attribute, graph: &EntityGraph, skip: &mut HashSet<u64>) {
match attr {
Attribute::EntityRef(n) => collect_refs_transitive(*n, graph, skip),
Attribute::List(items) => {
for item in items {
walk_refs_in_attr(item, graph, skip);
}
}
Attribute::Typed { value, .. } => walk_refs_in_attr(value, graph, skip),
_ => {}
}
}