use super::records::{MeshSurfaceSentinel, WireMembers, WireSide, WireTopology};
use crate::ids::{brep_id, IdFormat};
use crate::nurbs;
use crate::sab::{Record, Token};
use cadmpeg_ir::geometry::{
pcurve::PcurveGeometry, CurveGeometry, SolvedCurveGeometry, SolvedSurfaceGeometry,
SurfaceGeometry,
};
use cadmpeg_ir::ids::{
CoedgeId, EdgeId, FaceId, LoopId, ProceduralCurveId, ProceduralSurfaceId, RegionId, ShellId,
SurfaceId, VertexId,
};
use cadmpeg_ir::topology::Sense;
use std::collections::{HashMap, HashSet};
use super::attributes::unknown_record_id;
use super::geometry::{
analytic_procedural_surface, coedge_pcurve_ref, decode_curve, decode_surface,
is_analytic_curve, is_analytic_surface, is_coedge_record, is_edge_record, is_vertex_record,
pcurve_ranges_on_domain, procedural_surface_definition_is_exact_carrier, record_reversed,
reverse_procedural_curve_definition, sense_at, vertex_point_ref,
};
use super::{count_kind, id, AsmBrep, Carriers, DecodePurpose, Reachable, WireShellTopology};
pub(super) fn decode_analytic_carriers(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
records: &[Record],
) -> Result<(Carriers, HashSet<i64>), cadmpeg_core::CodecError> {
let mut surface_geo: HashMap<i64, SurfaceGeometry> = HashMap::new();
let mut inward_normal_surfaces = HashSet::new();
let mut curve_geo: HashMap<i64, CurveGeometry> = HashMap::new();
for r in records {
if is_analytic_surface(r.head()) {
if let Some((geometry, inward)) = decode_surface(ctx, r).transpose()? {
if inward {
ctx.insert_hash_set(
&mut inward_normal_surfaces,
i64::try_from(r.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM record index",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(r.index),
)
})?,
"ASM topology inward_normal_surfaces",
)?;
}
ctx.insert_hash_map(
&mut surface_geo,
i64::try_from(r.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM record index",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(r.index),
)
})?,
SurfaceGeometry::Solved(geometry),
"ASM topology surface_geo",
)?;
}
} else if is_analytic_curve(r.head()) {
if let Some(g) = decode_curve(ctx, r).transpose()? {
ctx.insert_hash_map(
&mut curve_geo,
i64::try_from(r.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM record index",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(r.index),
)
})?,
g,
"ASM topology curve_geo",
)?;
}
}
}
let carriers = Carriers {
surface_geo,
curve_geo,
..Carriers::default()
};
Ok((carriers, inward_normal_surfaces))
}
#[derive(Clone, Copy)]
pub(super) struct TopologyContext<'a, 'ctx, 'record> {
pub(super) ctx: &'a cadmpeg_core::decode::DecodeContext<'ctx>,
pub(super) by_index: &'a HashMap<i64, &'record Record>,
pub(super) token_table: &'a nurbs::toks::SubtypeTable,
pub(super) purpose: DecodePurpose,
pub(super) format: IdFormat,
}
pub(super) fn keep_faces_and_carriers(
inputs: TopologyContext<'_, '_, '_>,
out: &mut AsmBrep,
records: &[Record],
carriers: &mut Carriers,
reach: &mut Reachable,
) -> Result<(), cadmpeg_core::CodecError> {
let TopologyContext {
ctx,
by_index,
token_table,
purpose,
format,
} = inputs;
let Carriers {
surface_geo,
procedural_surface_defs,
..
} = &mut *carriers;
let Reachable {
faces: kept_faces,
surfaces: kept_surfaces,
unknown_surface_records,
cached_unknown_procedural_surfaces,
undecoded_carriers,
..
} = &mut *reach;
for r in records {
if r.head() != "face" {
continue;
}
if r.ref_at(5).is_none() {
continue;
}
let Some(surf_ref) = r.ref_at(7) else {
count_kind(
ctx,
&mut out.stats.missing_face_surface_kinds,
"null-reference",
)?;
continue;
};
let Some(surf_rec) = by_index.get(&surf_ref) else {
count_kind(
ctx,
&mut out.stats.missing_face_surface_kinds,
"dangling-reference",
)?;
continue;
};
ctx.insert_hash_set(
kept_faces,
i64::try_from(r.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM record index",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(r.index),
)
})?,
"ASM topology kept_faces",
)?;
if purpose == DecodePurpose::History {
let native_kind = (surf_rec.head() == "spline")
.then(|| nurbs::toks::owned_construction_subtype(ctx, &surf_rec.tokens))
.flatten()
.transpose()?;
if native_kind
.as_deref()
.is_some_and(|kind| kind.contains("blend"))
{
if let Some(procedural) = nurbs::proc_surface::procedural_surface_resolving_refs(
ctx,
&surf_rec.tokens,
token_table,
) {
ctx.insert_hash_map(
procedural_surface_defs,
surf_ref,
procedural?,
"ASM topology procedural_surface_defs",
)?;
}
}
if !surface_geo.contains_key(&surf_ref) {
ctx.insert_hash_map(
surface_geo,
surf_ref,
SurfaceGeometry::Solved(SolvedSurfaceGeometry::Unknown { record: None }),
"ASM topology surface_geo",
)?;
}
ctx.insert_hash_set(kept_surfaces, surf_ref, "ASM topology kept_surfaces")?;
continue;
}
if let Some(procedural) = nurbs::proc_surface::procedural_surface_resolving_refs(
ctx,
&surf_rec.tokens,
token_table,
) {
ctx.insert_hash_map(
procedural_surface_defs,
surf_ref,
procedural?,
"ASM topology procedural_surface_defs",
)?;
}
if let Some(procedural) = procedural_surface_defs.get(&surf_ref) {
if let Some(geometry) = analytic_procedural_surface(ctx, procedural.definition()) {
ctx.insert_hash_map(surface_geo, surf_ref, geometry?, "ASM topology surface_geo")?;
}
}
let exact_cacheless_construction =
procedural_surface_defs
.get(&surf_ref)
.is_some_and(|procedural| {
procedural.cache_fit_tolerance().is_none()
&& procedural_surface_definition_is_exact_carrier(procedural.definition())
});
if !exact_cacheless_construction && !surface_geo.contains_key(&surf_ref) {
if let Some(ns) =
nurbs::core::surface_cache_resolving_refs(ctx, &surf_rec.tokens, token_table)
{
ctx.insert_hash_map(
surface_geo,
surf_ref,
SurfaceGeometry::Solved(SolvedSurfaceGeometry::Nurbs(ns?)),
"ASM topology surface_geo",
)?;
if surf_rec.head() == "spline" && !procedural_surface_defs.contains_key(&surf_ref) {
ctx.insert_hash_set(
cached_unknown_procedural_surfaces,
surf_ref,
"ASM topology cached_unknown_procedural_surfaces",
)?;
count_kind(
ctx,
&mut out.stats.other_record_kinds,
"cached-procedural-surface-untyped",
)?;
}
out.stats.nurbs_surfaces += 1;
}
}
if !surface_geo.contains_key(&surf_ref) && procedural_surface_defs.contains_key(&surf_ref) {
let construction_is_exact_carrier =
procedural_surface_defs
.get(&surf_ref)
.is_some_and(|procedural| {
procedural_surface_definition_is_exact_carrier(procedural.definition())
});
ctx.insert_hash_map(
surface_geo,
surf_ref,
if construction_is_exact_carrier {
SurfaceGeometry::Procedural {
construction: brep_id!(
format,
ProceduralSurfaceId,
"procedural_surface",
surf_ref
),
cache: None,
}
} else {
SurfaceGeometry::Solved(SolvedSurfaceGeometry::Unknown {
record: Some(unknown_record_id(ctx, surf_rec, format)?),
})
},
"ASM topology surface_geo",
)?;
if !construction_is_exact_carrier {
ctx.insert_hash_set(
undecoded_carriers,
surf_ref,
"ASM topology undecoded_carriers",
)?;
}
}
if surface_geo.contains_key(&surf_ref) {
ctx.insert_hash_set(kept_surfaces, surf_ref, "ASM topology kept_surfaces")?;
} else {
ctx.insert_hash_set(
unknown_surface_records,
surf_ref,
"ASM topology unknown_surface_records",
)?;
ctx.insert_hash_set(
undecoded_carriers,
surf_ref,
"ASM topology undecoded_carriers",
)?;
if surf_rec.head() == "mesh_surface" && surf_rec.chunks().next().is_none() {
if !out.mesh_surface_sentinels.iter().any(|sentinel| {
usize::try_from(sentinel.record_index).ok() == Some(surf_rec.index)
}) {
ctx.reserve_vec(
&mut out.mesh_surface_sentinels,
1,
"ASM mesh surface sentinels",
)?;
out.mesh_surface_sentinels.push(MeshSurfaceSentinel {
source_namespace:
crate::brep::records::identity::NativeRecordNamespace::new(format),
surface: SurfaceId::from(id(format, surf_ref)),
record_index: u32::try_from(surf_rec.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM native record index",
u64::from(u32::MAX),
cadmpeg_core::decode::u64_from_index(surf_rec.index),
)
})?,
});
}
out.stats.mesh_surface_faces += 1;
} else {
let native_kind = if surf_rec.head() == "spline" {
nurbs::toks::owned_construction_subtype(ctx, &surf_rec.tokens).transpose()?
} else {
None
};
count_kind(
ctx,
&mut out.stats.unknown_surface_kinds,
native_kind.as_deref().unwrap_or_else(|| surf_rec.head()),
)?;
}
}
}
Ok(())
}
pub(super) fn walk_reachable_topology(
inputs: TopologyContext<'_, '_, '_>,
out: &mut AsmBrep,
carriers: &mut Carriers,
reach: &mut Reachable,
) -> Result<(), cadmpeg_core::CodecError> {
let TopologyContext {
ctx,
by_index,
token_table,
purpose,
format,
} = inputs;
let Carriers {
curve_geo,
procedural_curve_defs,
pcurve_geo,
pcurve_parameter_ranges,
..
} = &mut *carriers;
let Reachable {
faces: kept_faces,
loops: kept_loops,
coedges: kept_coedges,
edges: kept_edges,
vertices: kept_vertices,
points: kept_points,
curves: kept_curves,
pcurves: kept_pcurves,
undecoded_carriers,
..
} = &mut *reach;
let face_indices = ctx.collect_vec(kept_faces.iter().copied(), "ASM reachable face walk")?;
for face_idx in face_indices {
let Some(face) = by_index.get(&face_idx) else {
continue;
};
let mut loop_ref = face.ref_at(4);
let mut loop_guard = HashSet::new();
while let Some(li) = loop_ref {
if !ctx.insert_hash_set(&mut loop_guard, li, "ASM topology loop_guard")? {
break;
}
let Some(lp) = by_index.get(&li) else { break };
if lp.head() != "loop" {
break;
}
ctx.insert_hash_set(kept_loops, li, "ASM topology kept_loops")?;
if let Some(first_ce) = lp.ref_at(4) {
let mut ce_ref = Some(first_ce);
let mut ce_guard = HashSet::new();
while let Some(ci) = ce_ref {
if !ctx.insert_hash_set(&mut ce_guard, ci, "ASM topology ce_guard")? {
break;
}
let Some(ce) = by_index.get(&ci) else { break };
if !is_coedge_record(ce) {
break;
}
ctx.insert_hash_set(kept_coedges, ci, "ASM topology kept_coedges")?;
if let Some(pc) = coedge_pcurve_ref(ce) {
if let Some(prec) = by_index.get(&pc) {
if purpose == DecodePurpose::History {
if !pcurve_geo.contains_key(&super::PcurveRecordIndex(pc)) {
ctx.insert_hash_map(
pcurve_geo,
super::PcurveRecordIndex(pc),
PcurveGeometry::Line(
cadmpeg_ir::geometry::pcurve::LinePcurve::U_AXIS,
),
"ASM topology pcurve_geo",
)?;
}
ctx.insert_hash_set(kept_pcurves, pc, "ASM topology kept_pcurves")?;
} else {
let decoded = match (prec.chunk(3), prec.chunk(4)) {
(Some(Token::Long(0)), Some(Token::True | Token::False)) => {
if let Some(span) = nurbs::toks::payload_subtype_toks(
prec,
5,
"exp_par_cur",
) {
nurbs::pcurve::explicit_pcurve_cache(ctx, span)
.map(|pcurve| pcurve.map(|pcurve| (pcurve, true)))
} else if let Some(span) =
nurbs::toks::payload_subtype_toks(prec, 5, "ref")
{
match span.interior() {
[_name, Token::Long(index), ..] =>
nurbs::pcurve::explicit_pcurve_cache_from_subtype_ref(ctx,
*index,
token_table,
)
.map(|pcurve| pcurve.map(|pcurve| (pcurve, true))),
_ => None,
}
} else {
None
}
}
(Some(Token::Long(selector)), Some(Token::Ref(reference)))
if matches!(*selector, 1 | 2 | -1 | -2) =>
{
by_index
.get(reference)
.filter(|record| record.head() == "intcurve")
.and_then(|intcurve| {
nurbs::proc_curve::pcurve_for_selector_with_chart(
ctx,
&intcurve.tokens,
*selector,
token_table,
)
.map(|result| result.and_then(|(mut curve, native_chart)| {
if (*selector < 0) ^ record_reversed(intcurve) {
curve.reverse_parameterization(ctx)?;
}
Ok((curve, native_chart))
}))
})
}
_ => None,
}.transpose()?;
let edge =
ce.ref_at(6).and_then(|edge| by_index.get(&edge)).copied();
let decoded = decoded.map(|(mut decoded, native_chart)| -> Result<_, cadmpeg_core::CodecError> {
if native_chart {
if let Some(surface) = face
.ref_at(7)
.and_then(|surface| by_index.get(&surface))
{
let Some(()) = nurbs::proc_curve::normalize_pcurve_for_surface_record(
ctx,
surface.head(),
&surface.tokens,
&mut decoded,
).transpose()? else { return Ok(None); };
}
}
Ok(pcurve_ranges_on_domain(&decoded, edge)
.and_then(|ranges| ranges.into_iter().next())
.map(|range| (decoded, range)))
}).transpose()?.flatten();
if let Some((decoded, parameter_range)) = decoded {
ctx.insert_hash_map(
pcurve_geo,
super::PcurveRecordIndex(pc),
PcurveGeometry::Nurbs { nurbs: decoded },
"ASM topology pcurve_geo",
)?;
ctx.insert_hash_map(
pcurve_parameter_ranges,
super::CoedgeRecordIndex(ci),
parameter_range,
"ASM topology pcurve_parameter_ranges",
)?;
ctx.insert_hash_set(
kept_pcurves,
pc,
"ASM topology kept_pcurves",
)?;
} else {
count_kind(
ctx,
&mut out.stats.undecoded_pcurve_kinds,
prec.head(),
)?;
}
}
} else {
count_kind(
ctx,
&mut out.stats.undecoded_pcurve_kinds,
"dangling-reference",
)?;
}
}
if let Some(ei) = ce.ref_at(6) {
if let Some(edge) = by_index.get(&ei) {
if is_edge_record(edge)
&& ctx.insert_hash_set(kept_edges, ei, "ASM topology kept_edges")?
{
for slot in [3usize, 5] {
if let Some(vi) = edge.ref_at(slot) {
if let Some(v) = by_index.get(&vi) {
if is_vertex_record(v) {
ctx.insert_hash_set(
kept_vertices,
vi,
"ASM topology kept_vertices",
)?;
if let Some(pi) = vertex_point_ref(v) {
ctx.insert_hash_set(
kept_points,
pi,
"ASM topology kept_points",
)?;
}
}
}
}
}
match edge.ref_at(8) {
Some(cv) if curve_geo.contains_key(&cv) => {
ctx.insert_hash_set(
kept_curves,
cv,
"ASM topology kept_curves",
)?;
}
Some(cv) => {
if let Some(crec) = by_index.get(&cv) {
if purpose == DecodePurpose::History {
ctx.insert_hash_map(curve_geo, cv, CurveGeometry::Solved(SolvedCurveGeometry::Unknown { record: None }), "ASM topology curve_geo")?;
ctx.insert_hash_set(kept_curves, cv, "ASM topology kept_curves")?;
} else if let Some(decoded) =
nurbs::proc_curve::procedural_curve_resolving_refs(
ctx,
&crec.tokens,
token_table,
).transpose()?
{
let parsed_domain = nurbs::proc_curve::nurbs_curve_parameter_domain(&decoded.curve);
let mut curve = decoded.curve;
if record_reversed(crec) {
curve.reverse_parameterization(ctx)?;
}
ctx.insert_hash_map(curve_geo, cv, CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(curve)), "ASM topology curve_geo")?;
ctx.insert_hash_map(procedural_curve_defs, cv, super::ProceduralCurveSource::Cached {
construction: Box::new(decoded.construction),
cache_fit_tolerance: decoded.cache_fit_tolerance,
parsed_domain,
}, "ASM topology procedural_curve_defs")?;
out.stats.nurbs_curves += 1;
ctx.insert_hash_set(kept_curves, cv, "ASM topology kept_curves")?;
} else if let Some(definition) =
nurbs::proc_curve::cacheless_procedural_curve_resolving_refs(
ctx,
&crec.tokens,
token_table,
).transpose()?.and_then(|definition| definition.into_definition().ok())
.and_then(|mut definition| {
if record_reversed(crec) {
reverse_procedural_curve_definition(&mut definition).ok()?;
}
Some(definition)
})
{
ctx.insert_hash_map(curve_geo, cv, CurveGeometry::Procedural {
construction: brep_id!(
format,
ProceduralCurveId,
"procedural_curve",
cv
),
cache: None,
}, "ASM topology curve_geo")?;
ctx.insert_hash_map(procedural_curve_defs, cv, super::ProceduralCurveSource::Cacheless(Box::new(definition)), "ASM topology procedural_curve_defs")?;
ctx.insert_hash_set(kept_curves, cv, "ASM topology kept_curves")?;
} else {
ctx.insert_hash_set(undecoded_carriers, cv, "ASM topology undecoded_carriers")?;
count_kind(
ctx,
&mut out.stats.procedural_curve_kinds,
crec.head(),
)?;
}
} else {
count_kind(
ctx,
&mut out.stats.procedural_curve_kinds,
"dangling-reference",
)?;
}
}
_ => {}
}
}
}
}
ce_ref = ce.ref_at(3);
if ce_ref == Some(first_ce) {
break;
}
}
}
loop_ref = lp.ref_at(3);
}
}
Ok(())
}
pub(super) fn collect_wire_topology(
inputs: TopologyContext<'_, '_, '_>,
out: &mut AsmBrep,
records: &[Record],
saved_entity_limit: Option<i64>,
carriers: &mut Carriers,
reach: &mut Reachable,
) -> Result<WireShellTopology, cadmpeg_core::CodecError> {
let TopologyContext {
ctx,
by_index,
format,
..
} = inputs;
let mut wire_edges_by_shell = HashMap::<i64, Vec<i64>>::new();
let mut free_vertices_by_shell = HashMap::<i64, Vec<i64>>::new();
let mut saved_free_edges = Vec::new();
if let Some(limit) = saved_entity_limit {
for edge in records {
let edge_index = i64::try_from(edge.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM record index",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(edge.index),
)
})?;
if !(1..limit).contains(&edge_index) || !is_edge_record(edge) {
continue;
}
let already_owned = reach.edges.contains(&edge_index);
keep_wire_edge(inputs, out, edge_index, carriers, reach)?;
if !already_owned && reach.edges.contains(&edge_index) {
ctx.push_vec(&mut saved_free_edges, edge_index, "ASM saved free edges")?;
}
}
}
for shell in records.iter().filter(|record| record.head() == "shell") {
let shell_index = i64::try_from(shell.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM record index",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(shell.index),
)
})?;
let mut wire_guard = HashSet::new();
for root in shell_wire_roots(ctx, shell, by_index)? {
let mut wire_ref = Some(root);
while let Some(wire_index) = wire_ref {
if !ctx.insert_hash_set(&mut wire_guard, wire_index, "ASM topology wire_guard")? {
break;
}
let Some(wire) = by_index
.get(&wire_index)
.filter(|record| record.head() == "wire")
else {
break;
};
let side = match wire.chunk(7) {
Some(Token::True) => Some(WireSide::In),
Some(Token::False) => Some(WireSide::Out),
_ => None,
};
let mut wire_edges = Vec::new();
if let Some(first_coedge) = wire.ref_at(4) {
let mut coedge_ref = Some(first_coedge);
let mut coedge_guard = HashSet::new();
while let Some(coedge_index) = coedge_ref {
if !ctx.insert_hash_set(
&mut coedge_guard,
coedge_index,
"ASM topology coedge_guard",
)? {
break;
}
let Some(coedge) = by_index
.get(&coedge_index)
.filter(|record| is_coedge_record(record))
else {
break;
};
if let Some(edge_index) = coedge.ref_at(6) {
if !wire_edges.contains(&edge_index) {
ctx.push_vec(&mut wire_edges, edge_index, "ASM wire edges")?;
}
ctx.admit_hash_map_entry(
&mut wire_edges_by_shell,
&shell_index,
"ASM wire edges by shell",
)?;
let edges = wire_edges_by_shell.entry(shell_index).or_default();
if !edges.contains(&edge_index) {
ctx.push_vec(edges, edge_index, "ASM shell wire edges")?;
}
keep_wire_edge(inputs, out, edge_index, carriers, reach)?;
}
coedge_ref = coedge.ref_at(3);
if coedge_ref == Some(first_coedge) {
break;
}
}
}
let free_vertex = if wire.ref_at(4).is_none() {
wire.ref_at(6).filter(|vertex| {
by_index
.get(vertex)
.is_some_and(|record| is_vertex_record(record))
})
} else {
None
};
if let Some(vertex) = free_vertex {
ctx.insert_hash_set(
&mut reach.vertices,
vertex,
"ASM topology reach.vertices",
)?;
ctx.admit_hash_map_entry(
&mut free_vertices_by_shell,
&shell_index,
"ASM free vertices by shell",
)?;
let vertices = free_vertices_by_shell.entry(shell_index).or_default();
if !vertices.contains(&vertex) {
ctx.push_vec(vertices, vertex, "ASM shell free vertices")?;
}
if let Some(point) = by_index
.get(&vertex)
.and_then(|record| vertex_point_ref(record))
{
ctx.insert_hash_set(&mut reach.points, point, "ASM topology reach.points")?;
}
}
if let Some(side) = side {
ctx.reserve_vec(&mut out.wire_topologies, 1, "ASM wire topologies")?;
out.wire_topologies.push(WireTopology {
source_namespace:
crate::brep::records::identity::NativeRecordNamespace::new(format),
shell: ShellId::from(id(format, shell_index)),
record_index: u32::try_from(wire.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM native record index",
u64::from(u32::MAX),
cadmpeg_core::decode::u64_from_index(wire.index),
)
})?,
members: match free_vertex {
Some(vertex) => WireMembers::Vertex(VertexId::from(id(format, vertex))),
None => WireMembers::Edges(
ctx.collect_vec(
wire_edges
.into_iter()
.map(|edge| EdgeId::from(id(format, edge))),
"ASM wire member edges",
)?,
),
},
side,
});
}
wire_ref = wire.ref_at(3);
}
}
}
Ok(WireShellTopology {
wire_edges_by_shell,
free_vertices_by_shell,
saved_free_edges,
})
}
fn keep_wire_edge(
inputs: TopologyContext<'_, '_, '_>,
out: &mut AsmBrep,
edge_index: i64,
carriers: &mut Carriers,
reach: &mut Reachable,
) -> Result<(), cadmpeg_core::CodecError> {
let TopologyContext {
ctx,
by_index,
token_table,
purpose,
format,
} = inputs;
let Carriers {
curve_geo,
procedural_curve_defs,
..
} = carriers;
let Reachable {
edges: kept_edges,
vertices: kept_vertices,
points: kept_points,
curves: kept_curves,
undecoded_carriers,
..
} = reach;
let Some(edge) = by_index
.get(&edge_index)
.filter(|edge| is_edge_record(edge))
else {
return Ok(());
};
if !ctx.insert_hash_set(kept_edges, edge_index, "ASM topology kept_edges")? {
return Ok(());
}
for slot in [3usize, 5] {
if let Some(vertex_index) = edge.ref_at(slot) {
if let Some(vertex) = by_index
.get(&vertex_index)
.filter(|vertex| is_vertex_record(vertex))
{
ctx.insert_hash_set(kept_vertices, vertex_index, "ASM topology kept_vertices")?;
if let Some(point_index) = vertex_point_ref(vertex) {
ctx.insert_hash_set(kept_points, point_index, "ASM topology kept_points")?;
}
}
}
}
let Some(curve_index) = edge.ref_at(8) else {
return Ok(());
};
if curve_geo.contains_key(&curve_index) {
ctx.insert_hash_set(kept_curves, curve_index, "ASM topology kept_curves")?;
} else {
let Some(curve_record) = by_index.get(&curve_index) else {
count_kind(
ctx,
&mut out.stats.procedural_curve_kinds,
"dangling-reference",
)?;
return Ok(());
};
if purpose == DecodePurpose::History {
ctx.insert_hash_map(
curve_geo,
curve_index,
CurveGeometry::Solved(SolvedCurveGeometry::Unknown { record: None }),
"ASM topology curve_geo",
)?;
ctx.insert_hash_set(kept_curves, curve_index, "ASM topology kept_curves")?;
return Ok(());
}
if let Some(decoded) = nurbs::proc_curve::procedural_curve_resolving_refs(
ctx,
&curve_record.tokens,
token_table,
)
.transpose()?
{
let parsed_domain = nurbs::proc_curve::nurbs_curve_parameter_domain(&decoded.curve);
let mut curve = decoded.curve;
if record_reversed(curve_record) {
curve.reverse_parameterization(ctx)?;
}
ctx.insert_hash_map(
curve_geo,
curve_index,
CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(curve)),
"ASM topology curve_geo",
)?;
ctx.insert_hash_map(
procedural_curve_defs,
curve_index,
super::ProceduralCurveSource::Cached {
construction: Box::new(decoded.construction),
cache_fit_tolerance: decoded.cache_fit_tolerance,
parsed_domain,
},
"ASM topology procedural_curve_defs",
)?;
ctx.insert_hash_set(kept_curves, curve_index, "ASM topology kept_curves")?;
out.stats.nurbs_curves += 1;
} else if let Some(definition) =
nurbs::proc_curve::cacheless_procedural_curve_resolving_refs(
ctx,
&curve_record.tokens,
token_table,
)
.transpose()?
.and_then(|definition| definition.into_definition().ok())
.and_then(|mut definition| {
if record_reversed(curve_record) {
reverse_procedural_curve_definition(&mut definition).ok()?;
}
Some(definition)
})
{
ctx.insert_hash_map(
curve_geo,
curve_index,
CurveGeometry::Procedural {
construction: brep_id!(
format,
ProceduralCurveId,
"procedural_curve",
curve_index
),
cache: None,
},
"ASM topology curve_geo",
)?;
ctx.insert_hash_map(
procedural_curve_defs,
curve_index,
super::ProceduralCurveSource::Cacheless(Box::new(definition)),
"ASM topology procedural_curve_defs",
)?;
ctx.insert_hash_set(kept_curves, curve_index, "ASM topology kept_curves")?;
} else {
ctx.insert_hash_set(
undecoded_carriers,
curve_index,
"ASM topology undecoded_carriers",
)?;
count_kind(
ctx,
&mut out.stats.procedural_curve_kinds,
curve_record.head(),
)?;
}
}
Ok(())
}
pub(super) fn classify_edge_curve_senses(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
records: &[Record],
reach: &Reachable,
) -> Result<(HashSet<i64>, HashSet<i64>), cadmpeg_core::CodecError> {
let Reachable {
edges: kept_edges,
curves: kept_curves,
..
} = reach;
let mut reversed_curve_refs: HashSet<i64> = HashSet::new();
let mut forward_curve_refs: HashSet<i64> = HashSet::new();
for r in records {
if !is_edge_record(r)
|| !kept_edges.contains(
&(i64::try_from(r.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM record index",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(r.index),
)
})?),
)
{
continue;
}
let Some(curve) = r.ref_at(8).filter(|c| kept_curves.contains(c)) else {
continue;
};
match sense_at(r, 9) {
Sense::Reversed => ctx.insert_hash_set(
&mut reversed_curve_refs,
curve,
"ASM topology reversed_curve_refs",
)?,
Sense::Forward => ctx.insert_hash_set(
&mut forward_curve_refs,
curve,
"ASM topology forward_curve_refs",
)?,
};
}
Ok((reversed_curve_refs, forward_curve_refs))
}
pub(super) fn ring_coedges(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
loop_rec: &Record,
by_index: &HashMap<i64, &Record>,
kept: &HashSet<i64>,
format: IdFormat,
) -> Result<Vec<CoedgeId>, cadmpeg_core::CodecError> {
let id = |i: i64| CoedgeId::from(super::id(format, i));
let mut out = Vec::new();
let Some(first) = loop_rec.ref_at(4) else {
return Ok(out);
};
let mut cur = Some(first);
let mut guard = HashSet::new();
while let Some(ci) = cur {
if !ctx.insert_hash_set(&mut guard, ci, "ASM topology guard")? || !kept.contains(&ci) {
break;
}
ctx.push_vec(&mut out, id(ci), "ASM ring coedges")?;
let Some(ce) = by_index.get(&ci) else { break };
cur = ce.ref_at(3);
if cur == Some(first) {
break;
}
}
Ok(out)
}
pub(super) fn loop_chain(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
face_rec: &Record,
by_index: &HashMap<i64, &Record>,
kept: &HashSet<i64>,
format: IdFormat,
) -> Result<Vec<LoopId>, cadmpeg_core::CodecError> {
let id = |i: i64| LoopId::from(super::id(format, i));
let mut out = Vec::new();
let mut cur = face_rec.ref_at(4);
let mut guard = HashSet::new();
while let Some(li) = cur {
if !ctx.insert_hash_set(&mut guard, li, "ASM topology guard")? {
break;
}
if kept.contains(&li) {
ctx.push_vec(&mut out, id(li), "ASM face loops")?;
}
let Some(lp) = by_index.get(&li) else { break };
cur = lp.ref_at(3);
}
Ok(out)
}
fn face_chain(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
shell_rec: &Record,
by_index: &HashMap<i64, &Record>,
kept: &HashSet<i64>,
format: IdFormat,
) -> Result<Vec<FaceId>, cadmpeg_core::CodecError> {
let id = |i: i64| FaceId::from(super::id(format, i));
let mut out = Vec::new();
let mut cur = shell_rec.ref_at(5);
let mut guard = HashSet::new();
while let Some(fi) = cur {
if !ctx.insert_hash_set(&mut guard, fi, "ASM topology guard")? {
break;
}
if kept.contains(&fi) {
ctx.push_vec(&mut out, id(fi), "ASM shell faces")?;
}
let Some(f) = by_index.get(&fi) else { break };
cur = f.ref_at(3);
}
Ok(out)
}
pub(super) fn subshell_ancestor_shells(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
records: &[Record],
by_index: &HashMap<i64, &Record>,
) -> Result<HashMap<i64, i64>, cadmpeg_core::CodecError> {
let mut out = HashMap::new();
for record in records.iter().filter(|record| record.head() == "subshell") {
let mut owner = record.ref_at(3);
let mut guard = HashSet::new();
while let Some(index) = owner {
if !ctx.insert_hash_set(&mut guard, index, "ASM topology guard")? {
break;
}
let Some(parent) = by_index.get(&index) else {
break;
};
if parent.head() == "shell" {
ctx.insert_hash_map(
&mut out,
i64::try_from(record.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM record index",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(record.index),
)
})?,
index,
"ASM topology out",
)?;
break;
}
if parent.head() != "subshell" {
break;
}
owner = parent.ref_at(3);
}
}
Ok(out)
}
pub(super) fn shell_faces(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
shell: &Record,
by_index: &HashMap<i64, &Record>,
kept: &HashSet<i64>,
format: IdFormat,
) -> Result<Vec<FaceId>, cadmpeg_core::CodecError> {
let mut out = face_chain(ctx, shell, by_index, kept, format)?;
let mut pending = ctx.collect_vec(shell.ref_at(4), "ASM pending subshells")?;
let mut guard = HashSet::new();
while let Some(index) = pending.pop() {
if !ctx.insert_hash_set(&mut guard, index, "ASM topology guard")? {
break;
}
let Some(record) = by_index
.get(&index)
.filter(|record| record.head() == "subshell")
else {
break;
};
for face in face_chain_from(ctx, record.ref_at(6), by_index, kept, format)? {
ctx.push_vec(&mut out, face, "ASM shell faces")?;
}
if let Some(next) = record.ref_at(4) {
ctx.push_vec(&mut pending, next, "ASM pending subshells")?;
}
if let Some(child) = record.ref_at(5) {
ctx.push_vec(&mut pending, child, "ASM pending subshells")?;
}
}
Ok(out)
}
pub(super) fn shell_wire_roots(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
shell: &Record,
by_index: &HashMap<i64, &Record>,
) -> Result<Vec<i64>, cadmpeg_core::CodecError> {
let mut out = ctx.collect_vec(shell.ref_at(6), "ASM shell wire roots")?;
let mut pending = ctx.collect_vec(shell.ref_at(4), "ASM pending subshells")?;
let mut guard = HashSet::new();
while let Some(index) = pending.pop() {
if !ctx.insert_hash_set(&mut guard, index, "ASM topology guard")? {
break;
}
let Some(record) = by_index
.get(&index)
.filter(|record| record.head() == "subshell")
else {
break;
};
if let Some(wire) = record.ref_at(7) {
ctx.push_vec(&mut out, wire, "ASM shell wire roots")?;
}
if let Some(next) = record.ref_at(4) {
ctx.push_vec(&mut pending, next, "ASM pending subshells")?;
}
if let Some(child) = record.ref_at(5) {
ctx.push_vec(&mut pending, child, "ASM pending subshells")?;
}
}
Ok(out)
}
fn face_chain_from(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
mut current: Option<i64>,
by_index: &HashMap<i64, &Record>,
kept: &HashSet<i64>,
format: IdFormat,
) -> Result<Vec<FaceId>, cadmpeg_core::CodecError> {
let mut out = Vec::new();
let mut guard = HashSet::new();
while let Some(index) = current {
if !ctx.insert_hash_set(&mut guard, index, "ASM topology guard")? {
break;
}
if kept.contains(&index) {
ctx.push_vec(&mut out, FaceId::from(id(format, index)), "ASM shell faces")?;
}
let Some(face) = by_index.get(&index) else {
break;
};
current = face.ref_at(3);
}
Ok(out)
}
fn owned_child<'a>(
by_index: &HashMap<i64, &'a Record>,
index: i64,
heads: &[&str],
owner_slot: usize,
parent: &Record,
) -> Option<&'a Record> {
let parent = i64::try_from(parent.index).ok()?;
by_index.get(&index).copied().filter(|record| {
heads.contains(&record.head()) && record.ref_at(owner_slot) == Some(parent)
})
}
pub(super) fn shell_chain(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
region_rec: &Record,
by_index: &HashMap<i64, &Record>,
emitted_shells: &HashSet<i64>,
format: IdFormat,
) -> Result<Vec<ShellId>, cadmpeg_core::CodecError> {
let id = |i: i64| ShellId::from(super::id(format, i));
let mut out = Vec::new();
let mut cur = region_rec.ref_at(4);
let mut guard = HashSet::new();
while let Some(si) = cur {
if !ctx.insert_hash_set(&mut guard, si, "ASM topology guard")? {
break;
}
let Some(s) = owned_child(by_index, si, &["shell"], 7, region_rec) else {
break;
};
if emitted_shells.contains(&si) {
ctx.push_vec(&mut out, id(si), "ASM region shells")?;
}
cur = s.ref_at(3);
}
Ok(out)
}
pub(super) fn region_chain(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
body_rec: &Record,
by_index: &HashMap<i64, &Record>,
format: IdFormat,
) -> Result<Vec<RegionId>, cadmpeg_core::CodecError> {
let id = |i: i64| RegionId::from(super::id(format, i));
let mut out = Vec::new();
let mut cur = body_rec.ref_at(3);
let mut guard = HashSet::new();
while let Some(li) = cur {
if !ctx.insert_hash_set(&mut guard, li, "ASM topology guard")? {
break;
}
let Some(l) = owned_child(by_index, li, &["region", "lump"], 5, body_rec) else {
break;
};
ctx.push_vec(&mut out, id(li), "ASM body regions")?;
cur = l.ref_at(3);
}
Ok(out)
}
#[cfg(test)]
mod tests;