use super::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(super) enum StepBody {
SolidBrep(usize),
BrepWithVoids(usize),
SurfaceModelShell { model_ref: usize, shell_ref: usize },
}
impl StepBody {
fn order_key(self) -> (usize, usize) {
match self {
Self::SolidBrep(entity_ref) | Self::BrepWithVoids(entity_ref) => (entity_ref, 0),
Self::SurfaceModelShell {
model_ref,
shell_ref,
} => (model_ref, shell_ref),
}
}
}
pub(super) fn step_body_for_entity(entity_ref: usize, entity: &Entity) -> Option<StepBody> {
if entity.has("BREP_WITH_VOIDS") {
Some(StepBody::BrepWithVoids(entity_ref))
} else if entity.has("MANIFOLD_SOLID_BREP") || entity.has("FACETED_BREP") {
Some(StepBody::SolidBrep(entity_ref))
} else {
None
}
}
fn surface_model_shell_refs(resolver: &Resolver, model_ref: usize) -> Option<Vec<usize>> {
let args = resolver
.get(model_ref)
.ok()?
.find("SHELL_BASED_SURFACE_MODEL")?;
let list = args.get(1)?.as_list().ok()?;
let mut refs = Vec::with_capacity(list.len());
for value in list {
refs.push(value.as_ref_id().ok()?);
}
Some(refs)
}
fn push_step_bodies(
resolver: &Resolver,
entity_ref: usize,
entity: &Entity,
out: &mut Vec<StepBody>,
) {
if let Some(body) = step_body_for_entity(entity_ref, entity) {
out.push(body);
return;
}
if entity.has("SHELL_BASED_SURFACE_MODEL") {
if let Some(shell_refs) = surface_model_shell_refs(resolver, entity_ref) {
for shell_ref in shell_refs {
out.push(StepBody::SurfaceModelShell {
model_ref: entity_ref,
shell_ref,
});
}
}
}
}
pub(super) fn flat_step_bodies(
resolver: &Resolver,
entities: &HashMap<usize, Entity>,
) -> Vec<StepBody> {
let mut bodies: Vec<StepBody> = Vec::new();
for (id, entity) in entities.iter() {
push_step_bodies(resolver, *id, entity, &mut bodies);
}
dedupe_step_bodies(resolver, &mut bodies);
bodies
}
pub(super) fn build_step_body(
resolver: &Resolver,
body: StepBody,
) -> Result<(BrepSolid, builder::readings::BodyReadings), StepBodyError> {
build_step_body_captured_or_not(resolver, body, false).map(|(solid, _, readings)| (solid, readings))
}
fn build_step_body_captured_or_not(
resolver: &Resolver,
body: StepBody,
capture: bool,
) -> Result<(BrepSolid, Option<builder::readings::TrimCapture>, builder::readings::BodyReadings), StepBodyError> {
match body {
StepBody::SolidBrep(entity_ref) => build_solid_inner(resolver, entity_ref, capture),
StepBody::BrepWithVoids(entity_ref) => build_brep_with_voids_captured(resolver, entity_ref, capture),
StepBody::SurfaceModelShell { shell_ref, .. } => {
build_solid_from_shell_captured(resolver, shell_ref, capture)
}
}
}
pub(super) fn build_step_body_captured(
resolver: &Resolver,
body: StepBody,
) -> Result<(BrepSolid, Option<builder::readings::TrimCapture>, builder::readings::BodyReadings), StepBodyError> {
build_step_body_captured_or_not(resolver, body, true)
}
pub(super) fn step_body_appearance(
resolver: &Resolver,
styles: &StyleTable,
body: StepBody,
solid: &BrepSolid,
) -> BodyAppearance {
let mut appearance = BodyAppearance::default();
if styles.is_empty() {
return appearance;
}
let shell_refs = step_body_claimed_shells(resolver, body);
let mut body_items: Vec<usize> = match body {
StepBody::SolidBrep(entity_ref) | StepBody::BrepWithVoids(entity_ref) => vec![entity_ref],
StepBody::SurfaceModelShell { model_ref, .. } => vec![model_ref],
};
body_items.extend(shell_refs.as_ref().and_then(|shells| shells.first()).copied());
appearance.body = body_items
.into_iter()
.find_map(|item_ref| styles.color_for(item_ref));
let Some(shell_refs) = shell_refs else {
return appearance;
};
if shell_refs.len() != solid.shells.len() {
return appearance;
}
let mut faces: Vec<Option<ImportedColor>> = Vec::new();
let mut any = false;
for (shell_ref, shell) in shell_refs.into_iter().zip(&solid.shells) {
let Some(face_refs) = shell_face_refs(resolver, shell_ref) else {
return appearance;
};
if face_refs.len() != shell.faces.len() {
return appearance;
}
for face_ref in face_refs {
let color = styles.color_for(face_ref);
any |= color.is_some();
faces.push(color);
}
}
if any {
appearance.faces = faces;
}
appearance
}
fn shell_face_refs(resolver: &Resolver, shell_ref: usize) -> Option<Vec<usize>> {
let entity = resolver.get(shell_ref).ok()?;
let list = entity
.find("CLOSED_SHELL")
.or_else(|| entity.find("OPEN_SHELL"))?
.get(1)?
.as_list()
.ok()?;
list.iter().map(|value| value.as_ref_id().ok()).collect()
}
pub(super) fn step_body_claimed_shells(resolver: &Resolver, body: StepBody) -> Option<Vec<usize>> {
match body {
StepBody::SolidBrep(entity_ref) => resolver
.get(entity_ref)
.ok()?
.find("MANIFOLD_SOLID_BREP")
.or_else(|| resolver.get(entity_ref).ok()?.find("FACETED_BREP"))
.and_then(|args| args.get(1))
.and_then(|value| value.as_ref_id().ok())
.map(|shell_ref| vec![shell_ref]),
StepBody::BrepWithVoids(entity_ref) => {
let args = resolver.get(entity_ref).ok()?.find("BREP_WITH_VOIDS")?;
let mut claims = vec![args.get(1)?.as_ref_id().ok()?];
for oriented_value in args.get(2)?.as_list().ok()? {
let oriented_ref = oriented_value.as_ref_id().ok()?;
let oriented = resolver
.get(oriented_ref)
.ok()?
.find("ORIENTED_CLOSED_SHELL")?;
claims.push(oriented.get(2)?.as_ref_id().ok()?);
}
Some(claims)
}
StepBody::SurfaceModelShell { shell_ref, .. } => Some(vec![shell_ref]),
}
}
fn dedupe_step_bodies(resolver: &Resolver, bodies: &mut Vec<StepBody>) {
bodies.sort_unstable_by_key(|body| body.order_key());
let ordinary_claims: HashSet<usize> = bodies
.iter()
.filter(|body| matches!(body, StepBody::SolidBrep(_)))
.filter_map(|body| step_body_claimed_shells(resolver, *body))
.flatten()
.collect();
let mut seen = HashSet::default();
bodies.retain(|body| {
let Some(claims) = step_body_claimed_shells(resolver, *body) else {
return true;
};
if matches!(body, StepBody::BrepWithVoids(_))
&& claims.iter().any(|shell| ordinary_claims.contains(shell))
{
return false;
}
if claims.iter().any(|shell| seen.contains(shell)) {
return false;
}
seen.extend(claims);
true
});
}
pub(super) struct ImportedBodies {
pub(super) solids: Vec<BrepSolid>,
pub(super) appearances: Vec<BodyAppearance>,
pub(super) face_refs: Vec<Vec<usize>>,
pub(super) failed: usize,
pub(super) first_error: Option<String>,
pub(super) stated_precisions_mm: Vec<f64>,
pub(super) bounded_trims: Vec<Vec<builder::readings::BoundedTrim>>,
pub(super) closure_notes: Vec<builder::ClosureNote>,
pub(super) refused_bodies: Vec<KernelRefusal>,
pub(super) failed_refusals: Vec<KernelRefusal>,
}
pub(super) fn shell_face_refs_pub(resolver: &Resolver, shell_ref: usize) -> Option<Vec<usize>> {
shell_face_refs(resolver, shell_ref)
}
pub(super) fn collect_step_solids(text: &str) -> Result<ImportedBodies, String> {
if !text.contains("ISO-10303-21") {
return Err("step_import: not an ISO-10303-21 Part 21 file".into());
}
let debug_timing = std::env::var("BREP_DEBUG_STEP_LOOP").is_ok();
let parse_started = debug_timing.then(web_time::Instant::now);
let entities = parse_data_section(text)?;
if let Some(started) = parse_started {
eprintln!("PHASE parse: {:?} ({} entities)", started.elapsed(), entities.len());
}
let resolver = Resolver::new(&entities, derive_length_scale_mm(&entities));
let styles = StyleTable::read(&entities);
let stated_precisions_mm = stated_precisions_mm(&entities);
let mut refused: Vec<StepMappedItemError> = Vec::new();
let edges = structure_edges(&entities, &resolver);
if !edges.is_empty() {
let assembly_started = debug_timing.then(web_time::Instant::now);
let occurrences = resolve_assembly(&entities, &resolver, &styles, &edges);
if let Some(started) = assembly_started {
eprintln!("PHASE assembly: {:?}", started.elapsed());
}
if !occurrences.solids.is_empty() {
if std::env::var("BREP_DEBUG_STEP_ASM").is_ok() {
eprintln!(
"[step-asm] {} positioned occurrence(s) from {} structure edge(s):",
occurrences.solids.len(),
edges.len()
);
for line in &occurrences.debug {
eprintln!("[step-asm] {line}");
}
}
return Ok(ImportedBodies {
bounded_trims: Vec::new(),
closure_notes: Vec::new(),
refused_bodies: occurrences.refused_bodies,
failed_refusals: occurrences.failed_refusals,
solids: occurrences.solids,
appearances: occurrences.appearances,
face_refs: occurrences.face_refs,
failed: occurrences.failed,
first_error: occurrences.first_error,
stated_precisions_mm,
});
}
refused = occurrences.refusals;
}
let bodies = flat_step_bodies(&resolver, &entities);
let loop_started = debug_timing.then(web_time::Instant::now);
let build = |body: StepBody| {
(
matches!(body, StepBody::SurfaceModelShell { .. }),
build_step_body(&resolver, body).map(|(solid, readings)| {
let appearance = step_body_appearance(&resolver, &styles, body, &solid);
let face_refs = super::pmi::body_face_refs(&resolver, body, &solid);
(solid, appearance, face_refs, readings)
}),
)
};
type BuiltBody = (
bool,
Result<(BrepSolid, BodyAppearance, Vec<usize>, builder::readings::BodyReadings), StepBodyError>,
);
#[cfg(feature = "parallel")]
let results: Vec<BuiltBody> = {
use rayon::prelude::*;
bodies.into_par_iter().map(build).collect()
};
#[cfg(not(feature = "parallel"))]
let results: Vec<BuiltBody> = bodies.into_iter().map(build).collect();
if let Some(started) = loop_started {
eprintln!("PHASE reconstruct: {:?} ({} bodies)", started.elapsed(), results.len());
}
let mut solids = Vec::new();
let mut appearances = Vec::new();
let mut face_refs = Vec::new();
let mut bounded_trims = Vec::new();
let mut closure_notes = Vec::new();
let mut refused_bodies = Vec::new();
let mut failed_refusals = Vec::new();
let mut failed = refused.len();
let mut first_error = refused.first().map(ToString::to_string);
for (opportunistic, result) in results {
match result {
Ok((solid, _, _, _))
if opportunistic
&& solid.shells.iter().map(|s| s.faces.len()).sum::<usize>() <= 1 => {}
Ok((solid, appearance, refs, readings)) => {
solids.push(solid);
appearances.push(appearance);
face_refs.push(refs);
bounded_trims.push(readings.bounded);
closure_notes.push(readings.closure);
}
Err(error) if opportunistic && !error.closure_refusal() => {}
Err(error) => {
failed += 1;
if first_error.is_none() {
first_error = Some(error.to_string());
}
let closure = error.closure_refusal();
if let StepBodyError::Refused(refusal) = error {
if closure {
refused_bodies.push(refusal);
} else {
failed_refusals.push(refusal);
}
}
}
}
}
Ok(ImportedBodies {
solids,
appearances,
face_refs,
failed,
first_error,
stated_precisions_mm,
bounded_trims,
closure_notes,
refused_bodies,
failed_refusals,
})
}
pub(super) use crate::step_matrix::{mat4_mul, Mat4};
pub(super) fn mat4_identity() -> Mat4 {
crate::step_matrix::MAT4_IDENTITY
}
pub(super) fn mat4_determinant3(m: &Mat4) -> f64 {
m[0] * (m[5] * m[10] - m[6] * m[9]) - m[1] * (m[4] * m[10] - m[6] * m[8])
+ m[2] * (m[4] * m[9] - m[5] * m[8])
}
pub(super) fn frame_matrix(frame: &Frame) -> Mat4 {
[
frame.x.x,
frame.y.x,
frame.z.x,
frame.origin.x,
frame.x.y,
frame.y.y,
frame.z.y,
frame.origin.y,
frame.x.z,
frame.y.z,
frame.z.z,
frame.origin.z,
0.0,
0.0,
0.0,
1.0,
]
}
pub(super) fn mat4_affine_inverse(m: &Mat4) -> Result<Mat4, String> {
let (a, b, c) = (m[0], m[1], m[2]);
let (d, e, f) = (m[4], m[5], m[6]);
let (g, h, i) = (m[8], m[9], m[10]);
let det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
if det.abs() <= 1e-14 {
return Err("step_import: singular assembly transform".into());
}
let inv_det = 1.0 / det;
let r = [
(e * i - f * h) * inv_det,
(c * h - b * i) * inv_det,
(b * f - c * e) * inv_det,
(f * g - d * i) * inv_det,
(a * i - c * g) * inv_det,
(c * d - a * f) * inv_det,
(d * h - e * g) * inv_det,
(b * g - a * h) * inv_det,
(a * e - b * d) * inv_det,
];
let (tx, ty, tz) = (m[3], m[7], m[11]);
let it = [
-(r[0] * tx + r[1] * ty + r[2] * tz),
-(r[3] * tx + r[4] * ty + r[5] * tz),
-(r[6] * tx + r[7] * ty + r[8] * tz),
];
Ok([
r[0], r[1], r[2], it[0], r[3], r[4], r[5], it[1], r[6], r[7], r[8], it[2], 0.0, 0.0, 0.0,
1.0,
])
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum StructureEdgeKind {
Nauo,
Mapped,
}
#[derive(Clone, Copy)]
pub(crate) struct AssemblyEdge {
pub(crate) edge_id: usize,
pub(crate) parent_pd: usize,
pub(crate) child_pd: usize,
pub(crate) kind: StructureEdgeKind,
pub(crate) mapped: Option<MappedItem>,
}
fn nauo_edges(entities: &HashMap<usize, Entity>) -> Vec<AssemblyEdge> {
let mut edges: Vec<AssemblyEdge> = entities
.iter()
.filter_map(|(id, entity)| {
let args = entity.find("NEXT_ASSEMBLY_USAGE_OCCURRENCE")?;
let parent_pd = args.get(3)?.as_ref_id().ok()?;
let child_pd = args.get(4)?.as_ref_id().ok()?;
Some(AssemblyEdge {
edge_id: *id,
parent_pd,
child_pd,
kind: StructureEdgeKind::Nauo,
mapped: None,
})
})
.collect();
edges.sort_by_key(|edge| edge.edge_id);
edges
}
pub(crate) fn structure_edges(
entities: &HashMap<usize, Entity>,
resolver: &Resolver,
) -> Vec<AssemblyEdge> {
let mut edges = nauo_edges(entities);
if !entities.values().any(|entity| entity.has("MAPPED_ITEM")) {
return edges;
}
let owners = representation_owner_index(entities);
let identity = identity_relation_index(entities);
let mut carriers: Vec<(usize, Vec<MappedItem>)> = entities
.iter()
.filter_map(|(id, entity)| {
representation_items(entity)?;
let items = representation_mapped_items(resolver, *id);
(!items.is_empty()).then_some((*id, items))
})
.collect();
carriers.sort_unstable_by_key(|(rep, _)| *rep);
for (rep, items) in carriers {
let parent = node_of_representation(rep, &owners, &identity);
for item in items {
let child = owners.get(&item.mapped_rep).copied().unwrap_or(item.mapped_rep);
edges.push(AssemblyEdge {
edge_id: item.item,
parent_pd: parent,
child_pd: child,
kind: StructureEdgeKind::Mapped,
mapped: Some(item),
});
}
}
edges.sort_by_key(|edge| edge.edge_id);
edges
}
fn node_of_representation(
rep: usize,
owners: &HashMap<usize, usize>,
identity: &HashMap<usize, Vec<usize>>,
) -> usize {
if let Some(&owner) = owners.get(&rep) {
return owner;
}
let mut visited: HashSet<usize> = HashSet::default();
let mut queue: VecDeque<usize> = VecDeque::new();
queue.push_back(rep);
let mut best: Option<usize> = None;
while let Some(current) = queue.pop_front() {
if !visited.insert(current) {
continue;
}
if let Some(&owner) = owners.get(¤t) {
best = Some(best.map_or(owner, |previous: usize| previous.min(owner)));
}
for &other in identity.get(¤t).into_iter().flatten() {
if !visited.contains(&other) {
queue.push_back(other);
}
}
}
best.unwrap_or(rep)
}
fn representation_owner_index(entities: &HashMap<usize, Entity>) -> HashMap<usize, usize> {
let mut owners: HashMap<usize, usize> = HashMap::default();
for entity in entities.values() {
let Some(args) = entity.find("SHAPE_DEFINITION_REPRESENTATION") else {
continue;
};
let (Some(pds_id), Some(rep)) = (
args.first().and_then(|value| value.as_ref_id().ok()),
args.get(1).and_then(|value| value.as_ref_id().ok()),
) else {
continue;
};
let owner = entities
.get(&pds_id)
.and_then(|pds| pds.find("PRODUCT_DEFINITION_SHAPE"))
.and_then(|pds_args| pds_args.get(2))
.and_then(|value| value.as_ref_id().ok());
let Some(owner) = owner.filter(|owner| {
entities
.get(owner)
.is_some_and(|entity| entity.has("PRODUCT_DEFINITION"))
}) else {
continue;
};
owners
.entry(rep)
.and_modify(|existing| *existing = (*existing).min(owner))
.or_insert(owner);
}
owners
}
fn identity_relation_index(entities: &HashMap<usize, Entity>) -> HashMap<usize, Vec<usize>> {
let mut adjacency: HashMap<usize, Vec<usize>> = HashMap::default();
for entity in entities.values() {
if entity.has("REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION") {
continue;
}
let Some(args) = entity.find("SHAPE_REPRESENTATION_RELATIONSHIP") else {
continue;
};
if args.len() < 4 {
continue;
}
let (Ok(rep1), Ok(rep2)) = (args[2].as_ref_id(), args[3].as_ref_id()) else {
continue;
};
adjacency.entry(rep1).or_default().push(rep2);
adjacency.entry(rep2).or_default().push(rep1);
}
adjacency
}
pub(super) fn node_representations(
entities: &HashMap<usize, Entity>,
resolver: &Resolver,
node: usize,
) -> Vec<usize> {
let mut visited: HashSet<usize> = HashSet::default();
let mut queue: VecDeque<usize> = pd_root_srs(entities, node).into_iter().collect();
let mut reps = Vec::new();
while let Some(sr) = queue.pop_front() {
if !visited.insert(sr) {
continue;
}
if resolver.get(sr).is_ok() {
reps.push(sr);
}
for other in identity_related_srs(entities, sr) {
if !visited.contains(&other) {
queue.push_back(other);
}
}
}
reps.sort_unstable();
reps.dedup();
reps
}
pub(super) fn representation_args(entity: &Entity) -> Option<&[Value]> {
entity
.find("ADVANCED_BREP_SHAPE_REPRESENTATION")
.or_else(|| entity.find("FACETED_BREP_SHAPE_REPRESENTATION"))
.or_else(|| entity.find("MANIFOLD_SURFACE_SHAPE_REPRESENTATION"))
.or_else(|| entity.find("GEOMETRICALLY_BOUNDED_SURFACE_SHAPE_REPRESENTATION"))
.or_else(|| entity.find("SHAPE_REPRESENTATION"))
}
pub(super) fn representation_items(entity: &Entity) -> Option<&[Value]> {
representation_args(entity)?.get(1)?.as_list().ok()
}
pub(super) fn representation_context(entity: &Entity) -> Option<usize> {
representation_args(entity)?.get(2)?.as_ref_id().ok()
}
pub(super) fn pd_root_srs(entities: &HashMap<usize, Entity>, pd: usize) -> Vec<usize> {
if entities
.get(&pd)
.is_some_and(|entity| representation_args(entity).is_some())
{
return vec![pd];
}
let pds_ids: Vec<usize> = entities
.iter()
.filter_map(|(id, entity)| {
let args = entity.find("PRODUCT_DEFINITION_SHAPE")?;
(args.get(2)?.as_ref_id().ok()? == pd).then_some(*id)
})
.collect();
let mut srs: Vec<usize> = entities
.values()
.filter_map(|entity| {
let args = entity.find("SHAPE_DEFINITION_REPRESENTATION")?;
let definition = args.first()?.as_ref_id().ok()?;
let used = args.get(1)?.as_ref_id().ok()?;
pds_ids.contains(&definition).then_some(used)
})
.collect();
srs.sort_unstable();
srs.dedup();
srs
}
fn identity_related_srs(entities: &HashMap<usize, Entity>, sr: usize) -> Vec<usize> {
let mut out = Vec::new();
for entity in entities.values() {
if entity.has("REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION") {
continue;
}
let Some(args) = entity.find("SHAPE_REPRESENTATION_RELATIONSHIP") else {
continue;
};
if args.len() < 4 {
continue;
}
let (Ok(rep1), Ok(rep2)) = (args[2].as_ref_id(), args[3].as_ref_id()) else {
continue;
};
if rep1 == sr {
out.push(rep2);
} else if rep2 == sr {
out.push(rep1);
}
}
out
}
pub(super) fn pd_step_bodies(
entities: &HashMap<usize, Entity>,
resolver: &Resolver,
pd: usize,
) -> Vec<StepBody> {
let mut bodies = Vec::new();
for sr in node_representations(entities, resolver, pd) {
let Ok(entity) = resolver.get(sr) else {
continue;
};
let Some(items) = representation_items(entity) else {
continue;
};
for item in items {
if let Ok(item_id) = item.as_ref_id() {
if let Ok(item_entity) = resolver.get(item_id) {
push_step_bodies(resolver, item_id, item_entity, &mut bodies);
}
}
}
}
bodies.sort_unstable_by_key(|body| body.order_key());
bodies.dedup();
dedupe_step_bodies(resolver, &mut bodies);
bodies
}
pub(super) struct ResolvedEdges {
pub(super) edges: Vec<AssemblyEdge>,
pub(super) transforms: HashMap<usize, Mat4>,
pub(super) refusals: Vec<StepMappedItemError>,
}
pub(super) fn resolve_structure_edges(
entities: &HashMap<usize, Entity>,
edges: &[AssemblyEdge],
mut scale_for: impl FnMut(usize) -> f64,
) -> ResolvedEdges {
let mut resolved = ResolvedEdges {
edges: Vec::with_capacity(edges.len()),
transforms: HashMap::default(),
refusals: Vec::new(),
};
for edge in edges {
let parent_scale = scale_for(edge.parent_pd);
let transform = match edge.kind {
StructureEdgeKind::Nauo => {
let parent = Resolver::new(entities, parent_scale);
Ok(nauo_edge_transform(entities, &parent, edge))
}
StructureEdgeKind::Mapped => {
let child_scale = scale_for(edge.child_pd);
match &edge.mapped {
Some(item) => mapped_item_transform(entities, item, child_scale, parent_scale),
None => Ok(mat4_identity()),
}
}
};
match transform {
Ok(transform) => {
resolved.transforms.insert(edge.edge_id, transform);
resolved.edges.push(*edge);
}
Err(refusal) => resolved.refusals.push(refusal),
}
}
resolved
}
pub(crate) fn nauo_edge_transform(
entities: &HashMap<usize, Entity>,
resolver: &Resolver,
edge: &AssemblyEdge,
) -> Mat4 {
let child_srs = pd_root_srs(entities, edge.child_pd);
for entity in entities.values() {
let Some(args) = entity.find("CONTEXT_DEPENDENT_SHAPE_REPRESENTATION") else {
continue;
};
let Some(pds_id) = args.get(1).and_then(|value| value.as_ref_id().ok()) else {
continue;
};
let Ok(pds) = resolver.get(pds_id) else {
continue;
};
let owns_nauo = pds
.find("PRODUCT_DEFINITION_SHAPE")
.and_then(|pds_args| pds_args.get(2))
.and_then(|value| value.as_ref_id().ok())
== Some(edge.edge_id);
if !owns_nauo {
continue;
}
let Some(rep_rel_id) = args.first().and_then(|value| value.as_ref_id().ok()) else {
continue;
};
if let Some(transform) = resolve_rep_rel_transform(resolver, rep_rel_id, &child_srs) {
return transform;
}
}
mat4_identity()
}
fn resolve_rep_rel_transform(
resolver: &Resolver,
rep_rel_id: usize,
child_srs: &[usize],
) -> Option<Mat4> {
let entity = resolver.get(rep_rel_id).ok()?;
let idt_id = entity
.find("REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION")?
.first()?
.as_ref_id()
.ok()?;
let rel = entity.find("REPRESENTATION_RELATIONSHIP")?;
let rep1 = rel.get(2)?.as_ref_id().ok()?;
let rep2 = rel.get(3)?.as_ref_id().ok()?;
let idt = resolver.get(idt_id).ok()?;
let idt_args = idt.find("ITEM_DEFINED_TRANSFORMATION")?;
let item1 = idt_args.get(2)?.as_ref_id().ok()?;
let item2 = idt_args.get(3)?.as_ref_id().ok()?;
let m1 = frame_matrix(&resolver.placement(item1).ok()?);
let m2 = frame_matrix(&resolver.placement(item2).ok()?);
let forward = mat4_mul(&m2, &mat4_affine_inverse(&m1).ok()?);
if child_srs.contains(&rep2) && !child_srs.contains(&rep1) {
mat4_affine_inverse(&forward).ok()
} else {
Some(forward)
}
}
pub(super) struct AssemblyResult {
pub(super) solids: Vec<BrepSolid>,
pub(super) face_refs: Vec<Vec<usize>>,
pub(super) appearances: Vec<BodyAppearance>,
failed: usize,
first_error: Option<String>,
refused_bodies: Vec<KernelRefusal>,
failed_refusals: Vec<KernelRefusal>,
refusals: Vec<StepMappedItemError>,
debug: Vec<String>,
}
pub(super) fn assembly_roots(edges: &[AssemblyEdge]) -> Vec<usize> {
let children: HashSet<usize> = edges.iter().map(|edge| edge.child_pd).collect();
let mut roots: Vec<usize> = edges
.iter()
.map(|edge| edge.parent_pd)
.filter(|pd| !children.contains(pd))
.collect();
roots.sort_unstable();
roots.dedup();
roots
}
pub(super) struct OccurrenceNode {
pub(super) pd: usize,
pub(super) world: Mat4,
pub(super) name: String,
ancestors: Vec<usize>,
}
pub(super) fn walk_occurrences(
resolver: &Resolver,
edges: &[AssemblyEdge],
transforms: &HashMap<usize, Mat4>,
mut visit: impl FnMut(&OccurrenceNode),
) {
let mut stack: Vec<OccurrenceNode> = assembly_roots(edges)
.iter()
.rev()
.map(|&pd| OccurrenceNode {
pd,
world: mat4_identity(),
name: product_name(resolver, pd),
ancestors: vec![pd],
})
.collect();
while let Some(node) = stack.pop() {
visit(&node);
let mut child_edges: Vec<&AssemblyEdge> = edges
.iter()
.filter(|edge| edge.parent_pd == node.pd)
.collect();
child_edges.sort_by_key(|edge| edge.edge_id);
for edge in child_edges.into_iter().rev() {
if node.ancestors.contains(&edge.child_pd) {
continue; }
let transform = transforms
.get(&edge.edge_id)
.copied()
.unwrap_or_else(mat4_identity);
let world = mat4_mul(&node.world, &transform);
let mut ancestors = node.ancestors.clone();
ancestors.push(edge.child_pd);
stack.push(OccurrenceNode {
pd: edge.child_pd,
world,
name: format!("{}/{}", node.name, product_name(resolver, edge.child_pd)),
ancestors,
});
}
}
}
pub(super) fn resolve_assembly(
entities: &HashMap<usize, Entity>,
resolver: &Resolver,
styles: &StyleTable,
edges: &[AssemblyEdge],
) -> AssemblyResult {
let scale = resolver.length_scale;
let ResolvedEdges {
edges,
transforms,
refusals,
} = resolve_structure_edges(entities, edges, |_| scale);
let edges = edges.as_slice();
let mut result = AssemblyResult {
solids: Vec::new(),
face_refs: Vec::new(),
appearances: Vec::new(),
failed: 0,
first_error: None,
refusals: refusals.clone(),
refused_bodies: Vec::new(),
failed_refusals: Vec::new(),
debug: Vec::new(),
};
for refusal in &refusals {
result.failed += 1;
if result.first_error.is_none() {
result.first_error = Some(refusal.to_string());
}
}
let mut solid_cache: HashMap<StepBody, Result<(BrepSolid, BodyAppearance, Vec<usize>), StepBodyError>> =
HashMap::default();
let debug_on = std::env::var("BREP_DEBUG_STEP_ASM").is_ok();
walk_occurrences(resolver, edges, &transforms, |node| {
for body in pd_step_bodies(entities, resolver, node.pd) {
let base = solid_cache.entry(body).or_insert_with(|| {
build_step_body(resolver, body)
.map(|(solid, _readings)| {
let appearance = step_body_appearance(resolver, styles, body, &solid);
let face_refs = super::pmi::body_face_refs(resolver, body, &solid);
(solid, appearance, face_refs)
})
.map_err(|error| {
error.with_context(|text| {
format!("step_import: part body {body:?} failed to build: {text}")
})
})
});
let (base_solid, base_appearance, base_refs) = match base {
Ok((solid, appearance, refs)) => (solid.clone(), appearance.clone(), refs.clone()),
Err(error)
if matches!(body, StepBody::SurfaceModelShell { .. }) && !error.closure_refusal() =>
{
continue
}
Err(error) => {
result.failed += 1;
if result.first_error.is_none() {
result.first_error = Some(error.to_string());
}
let closure = error.closure_refusal();
if let StepBodyError::Refused(refusal) = error {
let list = if closure { &mut result.refused_bodies } else { &mut result.failed_refusals };
if !list.iter().any(|known| known == refusal) {
list.push(refusal.clone());
}
}
continue;
}
};
match AffineTransform::new(node.world).and_then(|transform| {
transform_brep(&base_solid, transform, transform.determinant3() < 0.0)
}) {
Ok(positioned) => {
if debug_on {
result.debug.push(format!(
"{} body{body:?} @ ({:.1}, {:.1}, {:.1})",
node.name, node.world[3], node.world[7], node.world[11]
));
}
result.solids.push(positioned);
result.appearances.push(base_appearance);
result.face_refs.push(base_refs);
}
Err(error) => {
result.failed += 1;
if result.first_error.is_none() {
result.first_error = Some(error);
}
}
}
}
});
result
}
fn product_entity<'a>(resolver: &'a Resolver, pd: usize) -> Option<&'a Entity> {
let pd_entity = resolver.get(pd).ok()?;
let formation_id = pd_entity
.find("PRODUCT_DEFINITION")?
.get(2)?
.as_ref_id()
.ok()?;
let formation = resolver.get(formation_id).ok()?;
let product_ref = formation
.find("PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE")
.or_else(|| formation.find("PRODUCT_DEFINITION_FORMATION"))?
.get(2)?
.as_ref_id()
.ok()?;
resolver.get(product_ref).ok()
}
fn product_field(resolver: &Resolver, pd: usize, index: usize) -> Option<String> {
match product_entity(resolver, pd)?.find("PRODUCT")?.get(index)? {
Value::Str(text) => Some(text.clone()),
_ => None,
}
}
pub(super) fn product_name(resolver: &Resolver, pd: usize) -> String {
if let Some(name) = representation_name(resolver, pd) {
return name;
}
product_field(resolver, pd, 1).unwrap_or_else(|| format!("pd#{pd}"))
}
fn representation_name(resolver: &Resolver, id: usize) -> Option<String> {
let entity = resolver.get(id).ok()?;
match representation_args(entity)?.first()? {
Value::Str(text) if !text.trim().is_empty() => Some(text.trim().to_string()),
_ => None,
}
}
pub(super) fn product_id(resolver: &Resolver, pd: usize) -> String {
product_field(resolver, pd, 0).unwrap_or_default()
}