#[allow(clippy::wildcard_imports)]
use super::*;
use super::substrate::StreamView;
pub(crate) trait ParasolidStreamRecords {
type Row: Copy;
type Record;
const ID_STEM: &'static str;
fn rows(view: &StreamView) -> &[Self::Row];
fn xmt(row: &Self::Row) -> u32;
fn record(id: String, stream_ordinal: u32, row: &Self::Row) -> Self::Record;
fn id(record: &Self::Record) -> &str;
}
pub(crate) fn per_parasolid_stream<P: ParasolidStreamRecords>(
parsed: &ParsedStreams,
) -> Vec<P::Record> {
let mut records = Vec::new();
for (stream_ordinal, stream) in parsed.iter() {
for row in P::rows(stream.view_for_records()) {
let id = format!("nx:s{stream_ordinal}:{}#{}", P::ID_STEM, P::xmt(row));
records.push(P::record(id, stream_ordinal as u32, row));
}
}
records.sort_by(|left, right| P::id(left).cmp(P::id(right)));
records
}
pub(crate) trait ParasolidScanRecords {
type Row;
type Record;
const ID_STEM: &'static str;
fn scan(bytes: &[u8]) -> Vec<Self::Row>;
fn xmt(row: &Self::Row) -> u32;
fn record(id: String, stream_ordinal: u32, row: Self::Row) -> Self::Record;
fn id(record: &Self::Record) -> &str;
}
pub(crate) fn per_parasolid_scan<P: ParasolidScanRecords>(streams: &[Stream]) -> Vec<P::Record> {
let mut records = Vec::new();
for (stream_ordinal, stream) in streams.iter().enumerate() {
if !stream.kind.is_parasolid() {
continue;
}
for row in P::scan(&stream.inflated) {
let id = format!("nx:s{stream_ordinal}:{}#{}", P::ID_STEM, P::xmt(&row));
records.push(P::record(id, stream_ordinal as u32, row));
}
}
records.sort_by(|left, right| P::id(left).cmp(P::id(right)));
records
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParasolidOffsetSurfaceRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub discriminator: char,
pub true_offset: bool,
pub support_xmt: u32,
pub distance: f64,
pub inflated_offset: u64,
}
pub(crate) fn parasolid_offset_surface_records(
parsed: &ParsedStreams,
) -> Vec<ParasolidOffsetSurfaceRecord> {
per_parasolid_stream::<ParasolidOffsetSurfaceRecord>(parsed)
}
impl ParasolidStreamRecords for ParasolidOffsetSurfaceRecord {
type Row = crate::topology::OffsetSurface;
type Record = ParasolidOffsetSurfaceRecord;
const ID_STEM: &'static str = "offset-surface-record";
fn rows(view: &StreamView) -> &[Self::Row] {
&view.offset_surfaces
}
fn xmt(row: &Self::Row) -> u32 {
row.xmt
}
fn record(id: String, stream_ordinal: u32, row: &Self::Row) -> Self::Record {
ParasolidOffsetSurfaceRecord {
id,
stream_ordinal,
xmt: row.xmt,
discriminator: row.discriminator,
true_offset: row.true_offset,
support_xmt: row.support,
distance: row.distance,
inflated_offset: row.pos as u64,
}
}
fn id(record: &Self::Record) -> &str {
&record.id
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParasolidTrimmedCurveRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub basis_xmt: u32,
pub points: [[f64; 3]; 2],
pub parameters: [f64; 2],
pub inflated_offset: u64,
}
pub(crate) fn parasolid_trimmed_curve_records(
parsed: &ParsedStreams,
) -> Vec<ParasolidTrimmedCurveRecord> {
per_parasolid_stream::<ParasolidTrimmedCurveRecord>(parsed)
}
impl ParasolidStreamRecords for ParasolidTrimmedCurveRecord {
type Row = crate::topology::TrimmedCurve;
type Record = ParasolidTrimmedCurveRecord;
const ID_STEM: &'static str = "trimmed-curve-record";
fn rows(view: &StreamView) -> &[Self::Row] {
&view.trimmed_curves
}
fn xmt(row: &Self::Row) -> u32 {
row.xmt
}
fn record(id: String, stream_ordinal: u32, row: &Self::Row) -> Self::Record {
ParasolidTrimmedCurveRecord {
id,
stream_ordinal,
xmt: row.xmt,
basis_xmt: row.basis,
points: row.points,
parameters: row.parameters,
inflated_offset: row.pos as u64,
}
}
fn id(record: &Self::Record) -> &str {
&record.id
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParasolidSurfaceCurveRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub surface_xmt: u32,
pub pcurve_xmt: u32,
pub original_curve_xmt: u32,
pub tolerance_to_original: f64,
pub inflated_offset: u64,
}
pub(crate) fn parasolid_surface_curve_records(
parsed: &ParsedStreams,
) -> Vec<ParasolidSurfaceCurveRecord> {
per_parasolid_stream::<ParasolidSurfaceCurveRecord>(parsed)
}
impl ParasolidStreamRecords for ParasolidSurfaceCurveRecord {
type Row = crate::topology::SurfaceCurve;
type Record = ParasolidSurfaceCurveRecord;
const ID_STEM: &'static str = "surface-curve-record";
fn rows(view: &StreamView) -> &[Self::Row] {
&view.surface_curves
}
fn xmt(row: &Self::Row) -> u32 {
row.xmt
}
fn record(id: String, stream_ordinal: u32, row: &Self::Row) -> Self::Record {
ParasolidSurfaceCurveRecord {
id,
stream_ordinal,
xmt: row.xmt,
surface_xmt: row.surface,
pcurve_xmt: row.pcurve,
original_curve_xmt: row.original,
tolerance_to_original: row.tolerance,
inflated_offset: row.pos as u64,
}
}
fn id(record: &Self::Record) -> &str {
&record.id
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidBlendBoundRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub header_references: [u32; 5],
pub sense: bool,
pub boundary_index: u32,
pub blend_surface_xmt: u32,
pub escaped: bool,
pub inflated_offset: u64,
}
pub fn parasolid_blend_bound_records(streams: &[Stream]) -> Vec<ParasolidBlendBoundRecord> {
per_parasolid_scan::<ParasolidBlendBoundRecord>(streams)
}
impl ParasolidScanRecords for ParasolidBlendBoundRecord {
type Row = crate::intersection::BlendBound;
type Record = ParasolidBlendBoundRecord;
const ID_STEM: &'static str = "blend-bound-record";
fn scan(bytes: &[u8]) -> Vec<Self::Row> {
crate::intersection::blend_bounds(bytes)
}
fn xmt(row: &Self::Row) -> u32 {
row.xmt
}
fn record(id: String, stream_ordinal: u32, row: Self::Row) -> Self::Record {
ParasolidBlendBoundRecord {
id,
stream_ordinal,
xmt: row.xmt,
header_references: row.header_references,
sense: row.sense,
boundary_index: row.boundary_index,
blend_surface_xmt: row.blend_surface,
escaped: row.escaped,
inflated_offset: row.pos as u64,
}
}
fn id(record: &Self::Record) -> &str {
&record.id
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParasolidTermUseRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub count: u32,
pub form: String,
pub point: [f64; 3],
pub framing: crate::intersection::TermUseFraming,
pub inflated_offset: u64,
}
pub fn parasolid_term_use_records(streams: &[Stream]) -> Vec<ParasolidTermUseRecord> {
per_parasolid_scan::<ParasolidTermUseRecord>(streams)
}
impl ParasolidScanRecords for ParasolidTermUseRecord {
type Row = crate::intersection::TermUse;
type Record = ParasolidTermUseRecord;
const ID_STEM: &'static str = "term-use-record";
fn scan(bytes: &[u8]) -> Vec<Self::Row> {
crate::intersection::term_use_records(bytes)
}
fn xmt(row: &Self::Row) -> u32 {
row.xmt
}
fn record(id: String, stream_ordinal: u32, row: Self::Row) -> Self::Record {
ParasolidTermUseRecord {
id,
stream_ordinal,
xmt: row.xmt,
count: row.count,
form: String::from_utf8_lossy(&row.form).into_owned(),
point: [row.point.x, row.point.y, row.point.z],
framing: row.framing,
inflated_offset: row.pos as u64,
}
}
fn id(record: &Self::Record) -> &str {
&record.id
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParasolidSupportUvRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub count: u32,
pub marker: u8,
pub values: Vec<f64>,
pub framing: crate::intersection::SupportUvFraming,
pub inflated_offset: u64,
}
pub fn parasolid_support_uv_records(streams: &[Stream]) -> Vec<ParasolidSupportUvRecord> {
per_parasolid_scan::<ParasolidSupportUvRecord>(streams)
}
impl ParasolidScanRecords for ParasolidSupportUvRecord {
type Row = crate::intersection::SupportUvRecord;
type Record = ParasolidSupportUvRecord;
const ID_STEM: &'static str = "support-uv-record";
fn scan(bytes: &[u8]) -> Vec<Self::Row> {
crate::intersection::support_uv_records(bytes)
}
fn xmt(row: &Self::Row) -> u32 {
row.xmt
}
fn record(id: String, stream_ordinal: u32, row: Self::Row) -> Self::Record {
ParasolidSupportUvRecord {
id,
stream_ordinal,
xmt: row.xmt,
count: row.count,
marker: row.marker,
values: row.values,
framing: row.framing,
inflated_offset: row.pos as u64,
}
}
fn id(record: &Self::Record) -> &str {
&record.id
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParasolidChartRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub count: u32,
pub base_parameter: f64,
pub base_scale: f64,
pub chart_count: u32,
pub chordal_error: f64,
pub angular_error: f64,
pub parameter_errors: [f64; 2],
pub points: Vec<[f64; 3]>,
pub native_parameters: Option<Vec<f64>>,
pub ext_support_uv: [Option<Vec<[f64; 2]>>; 2],
pub point_layout: crate::intersection::ChartPointLayout,
pub framing: crate::intersection::ChartFraming,
pub inflated_offset: u64,
}
pub fn parasolid_chart_records(streams: &[Stream]) -> Vec<ParasolidChartRecord> {
let mut records = Vec::new();
for (stream_ordinal, stream) in streams.iter().enumerate() {
if !stream.kind.is_parasolid() {
continue;
}
for chart in crate::intersection::chart_source_records(&stream.inflated) {
records.push(ParasolidChartRecord {
id: format!(
"nx:s{stream_ordinal}:chart-record#{}-{}",
chart.xmt, chart.pos
),
stream_ordinal: stream_ordinal as u32,
xmt: chart.xmt,
count: chart.count,
base_parameter: chart.base_parameter,
base_scale: chart.base_scale,
chart_count: chart.chart_count,
chordal_error: chart.chordal_error,
angular_error: chart.angular_error,
parameter_errors: chart.parameter_errors,
points: chart
.points
.into_iter()
.map(|point| [point.x, point.y, point.z])
.collect(),
native_parameters: chart.native_parameters,
ext_support_uv: chart.ext_support_uv,
point_layout: chart.point_layout,
framing: chart.framing,
inflated_offset: chart.pos as u64,
});
}
}
records.sort_by(|left, right| left.id.cmp(&right.id));
records
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidIntersectionRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub header_references: [u32; 5],
pub sense: bool,
pub construction_references: [u32; 6],
pub delta_twin: bool,
pub inflated_offset: u64,
}
pub(crate) fn parasolid_intersection_records(
parsed: &ParsedStreams,
) -> Vec<ParasolidIntersectionRecord> {
per_parasolid_stream::<ParasolidIntersectionRecord>(parsed)
}
impl ParasolidStreamRecords for ParasolidIntersectionRecord {
type Row = crate::topology::CompositeCurve;
type Record = ParasolidIntersectionRecord;
const ID_STEM: &'static str = "intersection-record";
fn rows(view: &StreamView) -> &[Self::Row] {
&view.intersections.constructions
}
fn xmt(row: &Self::Row) -> u32 {
row.xmt
}
fn record(id: String, stream_ordinal: u32, row: &Self::Row) -> Self::Record {
ParasolidIntersectionRecord {
id,
stream_ordinal,
xmt: row.xmt,
header_references: row.header_references,
sense: row.sense,
construction_references: row.references,
delta_twin: row.delta_twin,
inflated_offset: row.pos as u64,
}
}
fn id(record: &Self::Record) -> &str {
&record.id
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParasolidBlendSurfaceRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub support_xmts: [u32; 2],
pub spine_xmt: u32,
pub offsets: [f64; 2],
pub thumb_weights: [f64; 2],
pub inflated_offset: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidAttributeDefinition {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u16,
pub name: String,
pub field_count: u32,
pub field_record_xmt: u16,
pub field_record_references: [u16; 2],
pub field_record_header_words: [u16; 2],
pub field_descriptor_prefix: [u8; 26],
#[serde(default, skip_serializing_if = "Option::is_none")]
pub field_storage: Option<ParasolidAttributeFieldStorage>,
pub field_codes: Vec<u8>,
pub inflated_offset: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ParasolidAttributeFieldStorage {
Void,
Component,
Double,
}
pub(crate) fn parasolid_attribute_field_storage(
descriptor: &[u8; 26],
) -> Option<ParasolidAttributeFieldStorage> {
(descriptor[4] == 0x03).then_some(())?;
match descriptor[5] {
0x00 => Some(ParasolidAttributeFieldStorage::Void),
0x05 => Some(ParasolidAttributeFieldStorage::Component),
0x06 => Some(ParasolidAttributeFieldStorage::Double),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidTopologyAttributeListReference {
pub id: String,
pub stream_ordinal: u32,
pub topology_type: u8,
pub topology_xmt: u32,
pub attribute_list_xmt: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attribute_list_record: Option<String>,
pub inflated_offset: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidEntity51Record {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub flags: u32,
pub sequence: u32,
pub discriminator: u16,
pub references: Vec<u32>,
pub byte_len: u64,
pub inflated_offset: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidEntity54StringRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub value: String,
pub byte_len: u64,
pub inflated_offset: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidEntity52IntegerRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub values: Vec<u32>,
pub byte_len: u64,
pub inflated_offset: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParasolidEntity53DoubleRecord {
pub id: String,
pub stream_ordinal: u32,
pub xmt: u32,
pub values: Vec<f64>,
pub byte_len: u64,
pub inflated_offset: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ParasolidEntity51NumericKind {
UnsignedIntegers,
Doubles,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidEntity51NumericUse {
pub id: String,
pub stream_ordinal: u32,
pub entity_51_record: String,
pub reference_ordinal: u32,
pub referenced_xmt: u32,
pub kind: ParasolidEntity51NumericKind,
pub value_record: String,
pub inflated_offset: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidEntity51StringUse {
pub id: String,
pub stream_ordinal: u32,
pub entity_51_record: String,
pub reference_ordinal: u32,
pub referenced_xmt: u32,
pub string_record: String,
pub inflated_offset: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidAttributeClassUse {
pub id: String,
pub stream_ordinal: u32,
pub entity_51_record: String,
pub class_discriminator: u16,
pub definition_xmt: u16,
pub attribute_definition: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParasolidTopologyAttributeClassUse {
pub id: String,
pub topology_attribute_reference: String,
pub entity_51_record: String,
pub class_discriminator: u16,
pub definition_xmt: u16,
pub attribute_definition: String,
}
pub fn parasolid_attribute_definitions(streams: &[Stream]) -> Vec<ParasolidAttributeDefinition> {
streams
.iter()
.enumerate()
.filter(|(_, stream)| stream.kind.is_parasolid())
.flat_map(|(stream_ordinal, stream)| {
crate::parasolid::attribute_definitions(&stream.inflated)
.into_iter()
.map(move |definition| ParasolidAttributeDefinition {
id: format!(
"nx:s{stream_ordinal}:attribute-definition#{}",
definition.xmt
),
stream_ordinal: stream_ordinal as u32,
xmt: definition.xmt,
name: definition.name.to_string(),
field_count: definition.field_count,
field_record_xmt: definition.field_record_xmt,
field_record_references: definition.field_record_references,
field_record_header_words: definition.field_record_header_words,
field_descriptor_prefix: definition.field_descriptor_prefix,
field_storage: parasolid_attribute_field_storage(
&definition.field_descriptor_prefix,
),
field_codes: definition.field_codes.to_vec(),
inflated_offset: definition.offset as u64,
})
})
.collect()
}
pub(crate) fn parasolid_blend_surface_records(
parsed: &ParsedStreams,
) -> Vec<ParasolidBlendSurfaceRecord> {
per_parasolid_stream::<ParasolidBlendSurfaceRecord>(parsed)
}
impl ParasolidStreamRecords for ParasolidBlendSurfaceRecord {
type Row = crate::topology::BlendSurface;
type Record = ParasolidBlendSurfaceRecord;
const ID_STEM: &'static str = "blend-surface-record";
fn rows(view: &StreamView) -> &[Self::Row] {
&view.blend_surfaces
}
fn xmt(row: &Self::Row) -> u32 {
row.xmt
}
fn record(id: String, stream_ordinal: u32, row: &Self::Row) -> Self::Record {
ParasolidBlendSurfaceRecord {
id,
stream_ordinal,
xmt: row.xmt,
support_xmts: row.supports,
spine_xmt: row.spine,
offsets: row.offsets,
thumb_weights: row.thumb_weights,
inflated_offset: row.pos as u64,
}
}
fn id(record: &Self::Record) -> &str {
&record.id
}
}
pub(crate) fn parasolid_topology_attribute_list_references(
parsed: &ParsedStreams,
entity_records: &[ParasolidEntity51Record],
) -> Vec<ParasolidTopologyAttributeListReference> {
let mut records_by_identity = BTreeMap::<(u32, u32), Vec<&str>>::new();
for record in entity_records {
records_by_identity
.entry((record.stream_ordinal, record.xmt))
.or_default()
.push(record.id.as_str());
}
let mut references = Vec::new();
for (stream_ordinal, stream) in parsed.iter() {
let graph = &stream.view_for_records().graph;
for topology_type in [13, 14, 15, 16, 17, 18] {
for node in graph.of_kind(topology_type) {
let attribute_list_xmt = match topology_type {
13 => node.shell_fields().map(|fields| fields.attributes),
14 => node.face_fields().map(|fields| fields.attributes),
15 => node.loop_fields().map(|fields| fields.attributes),
16 => node.edge_fields().map(|fields| fields.attributes),
17 => node.fin_fields().map(|fields| fields.attributes),
18 => node.vertex_fields().map(|fields| fields.attributes),
_ => unreachable!("bounded topology family"),
};
let Some(attribute_list_xmt) = attribute_list_xmt.filter(|value| *value > 1) else {
continue;
};
let Some(inflated_offset) = node.attribute_field_offset() else {
continue;
};
references.push(ParasolidTopologyAttributeListReference {
id: format!(
"nx:s{stream_ordinal}:topology-attribute-list-reference#{topology_type}-{}",
node.xmt
),
stream_ordinal: stream_ordinal as u32,
topology_type,
topology_xmt: node.xmt,
attribute_list_xmt,
attribute_list_record: records_by_identity
.get(&(stream_ordinal as u32, attribute_list_xmt))
.and_then(|records| {
let [record] = records.as_slice() else {
return None;
};
Some((*record).to_string())
}),
inflated_offset: inflated_offset as u64,
});
}
}
}
references
}
pub fn parasolid_entity_51_records(streams: &[Stream]) -> Vec<ParasolidEntity51Record> {
let mut records = streams
.iter()
.enumerate()
.filter(|(_, stream)| stream.kind.is_parasolid())
.flat_map(|(stream_ordinal, stream)| {
crate::parasolid::entity_51_records(&stream.inflated)
.into_iter()
.map(move |record| ParasolidEntity51Record {
id: format!(
"nx:s{stream_ordinal}:entity-51#{}-{}",
record.xmt, record.offset
),
stream_ordinal: stream_ordinal as u32,
xmt: record.xmt,
flags: record.flags,
sequence: record.sequence,
discriminator: record.discriminator,
references: record.references,
byte_len: record.byte_len as u64,
inflated_offset: record.offset as u64,
})
})
.collect::<Vec<_>>();
records.sort_by(|first, second| first.id.cmp(&second.id));
records
}
pub fn parasolid_entity_54_string_records(
streams: &[Stream],
) -> Vec<ParasolidEntity54StringRecord> {
let mut records = streams
.iter()
.enumerate()
.filter(|(_, stream)| stream.kind.is_parasolid())
.flat_map(|(stream_ordinal, stream)| {
crate::parasolid::entity_54_string_records(&stream.inflated)
.into_iter()
.map(move |record| ParasolidEntity54StringRecord {
id: format!(
"nx:s{stream_ordinal}:entity-54-string#{}-{}",
record.xmt, record.offset
),
stream_ordinal: stream_ordinal as u32,
xmt: record.xmt,
value: record.value.to_string(),
byte_len: record.byte_len as u64,
inflated_offset: record.offset as u64,
})
})
.collect::<Vec<_>>();
records.sort_by(|first, second| first.id.cmp(&second.id));
records
}
pub fn parasolid_entity_52_integer_records(
streams: &[Stream],
) -> Vec<ParasolidEntity52IntegerRecord> {
let mut records = streams
.iter()
.enumerate()
.filter(|(_, stream)| stream.kind.is_parasolid())
.flat_map(|(stream_ordinal, stream)| {
crate::parasolid::entity_52_integer_records(&stream.inflated)
.into_iter()
.map(move |record| ParasolidEntity52IntegerRecord {
id: format!(
"nx:s{stream_ordinal}:entity-52-integers#{}-{}",
record.xmt, record.offset
),
stream_ordinal: stream_ordinal as u32,
xmt: record.xmt,
values: record.values,
byte_len: record.byte_len as u64,
inflated_offset: record.offset as u64,
})
})
.collect::<Vec<_>>();
records.sort_by(|first, second| first.id.cmp(&second.id));
records
}
pub fn parasolid_entity_53_double_records(
streams: &[Stream],
) -> Vec<ParasolidEntity53DoubleRecord> {
let mut records = streams
.iter()
.enumerate()
.filter(|(_, stream)| stream.kind.is_parasolid())
.flat_map(|(stream_ordinal, stream)| {
crate::parasolid::entity_53_double_records(&stream.inflated)
.into_iter()
.map(move |record| ParasolidEntity53DoubleRecord {
id: format!(
"nx:s{stream_ordinal}:entity-53-doubles#{}-{}",
record.xmt, record.offset
),
stream_ordinal: stream_ordinal as u32,
xmt: record.xmt,
values: record.values,
byte_len: record.byte_len as u64,
inflated_offset: record.offset as u64,
})
})
.collect::<Vec<_>>();
records.sort_by(|first, second| first.id.cmp(&second.id));
records
}
pub fn parasolid_entity_51_numeric_uses(
entities: &[ParasolidEntity51Record],
integers: &[ParasolidEntity52IntegerRecord],
doubles: &[ParasolidEntity53DoubleRecord],
) -> Vec<ParasolidEntity51NumericUse> {
let mut values = BTreeMap::<(u32, u32), Vec<(ParasolidEntity51NumericKind, &str)>>::new();
for record in integers {
values
.entry((record.stream_ordinal, record.xmt))
.or_default()
.push((ParasolidEntity51NumericKind::UnsignedIntegers, &record.id));
}
for record in doubles {
values
.entry((record.stream_ordinal, record.xmt))
.or_default()
.push((ParasolidEntity51NumericKind::Doubles, &record.id));
}
let mut uses = Vec::new();
for entity in entities {
for (reference_ordinal, referenced_xmt) in entity.references.iter().copied().enumerate() {
let Some([(kind, value_record)]) = values
.get(&(entity.stream_ordinal, referenced_xmt))
.map(Vec::as_slice)
else {
continue;
};
uses.push(ParasolidEntity51NumericUse {
id: format!(
"nx:s{}:entity-51-numeric-use#{}-{}-{reference_ordinal}",
entity.stream_ordinal, entity.xmt, entity.inflated_offset
),
stream_ordinal: entity.stream_ordinal,
entity_51_record: entity.id.clone(),
reference_ordinal: reference_ordinal as u32,
referenced_xmt,
kind: *kind,
value_record: (*value_record).to_string(),
inflated_offset: entity.inflated_offset,
});
}
}
uses.sort_by(|first, second| first.id.cmp(&second.id));
uses
}
pub fn parasolid_entity_51_string_uses(
entities: &[ParasolidEntity51Record],
strings: &[ParasolidEntity54StringRecord],
) -> Vec<ParasolidEntity51StringUse> {
let mut strings_by_identity = BTreeMap::<(u32, u32), Vec<&str>>::new();
for string in strings {
strings_by_identity
.entry((string.stream_ordinal, string.xmt))
.or_default()
.push(string.id.as_str());
}
let mut uses = Vec::new();
for entity in entities {
for (reference_ordinal, referenced_xmt) in entity.references.iter().copied().enumerate() {
let Some([string]) = strings_by_identity
.get(&(entity.stream_ordinal, referenced_xmt))
.map(Vec::as_slice)
else {
continue;
};
uses.push(ParasolidEntity51StringUse {
id: format!(
"nx:s{}:entity-51-string-use#{}-{}-{reference_ordinal}",
entity.stream_ordinal, entity.xmt, entity.inflated_offset
),
stream_ordinal: entity.stream_ordinal,
entity_51_record: entity.id.clone(),
reference_ordinal: reference_ordinal as u32,
referenced_xmt,
string_record: (*string).to_string(),
inflated_offset: entity.inflated_offset,
});
}
}
uses.sort_by(|first, second| first.id.cmp(&second.id));
uses
}
pub fn parasolid_topology_attribute_class_uses(
topology_references: &[ParasolidTopologyAttributeListReference],
class_uses: &[ParasolidAttributeClassUse],
) -> Vec<ParasolidTopologyAttributeClassUse> {
let class_uses = class_uses
.iter()
.map(|class_use| (class_use.entity_51_record.as_str(), class_use))
.collect::<BTreeMap<_, _>>();
let mut uses = Vec::new();
for reference in topology_references {
let Some(entity_id) = reference.attribute_list_record.as_deref() else {
continue;
};
let Some(class_use) = class_uses.get(entity_id) else {
continue;
};
uses.push(ParasolidTopologyAttributeClassUse {
id: format!(
"nx:s{}:topology-attribute-class-use#{}-{}",
reference.stream_ordinal, reference.topology_type, reference.topology_xmt
),
topology_attribute_reference: reference.id.clone(),
entity_51_record: class_use.entity_51_record.clone(),
class_discriminator: class_use.class_discriminator,
definition_xmt: class_use.definition_xmt,
attribute_definition: class_use.attribute_definition.clone(),
});
}
uses.sort_by(|first, second| first.id.cmp(&second.id));
uses
}
pub fn parasolid_attribute_class_uses(
entities: &[ParasolidEntity51Record],
definitions: &[ParasolidAttributeDefinition],
) -> Vec<ParasolidAttributeClassUse> {
let mut definitions_by_identity =
BTreeMap::<(u32, u16), Vec<&ParasolidAttributeDefinition>>::new();
for definition in definitions {
definitions_by_identity
.entry((definition.stream_ordinal, definition.xmt))
.or_default()
.push(definition);
}
let mut uses = entities
.iter()
.filter_map(|entity| {
let definition_xmt = entity.discriminator.checked_add(1)?;
let [definition] = definitions_by_identity
.get(&(entity.stream_ordinal, definition_xmt))?
.as_slice()
else {
return None;
};
Some(ParasolidAttributeClassUse {
id: format!(
"nx:s{}:attribute-class-use#{}-{}",
entity.stream_ordinal, entity.xmt, entity.inflated_offset
),
stream_ordinal: entity.stream_ordinal,
entity_51_record: entity.id.clone(),
class_discriminator: entity.discriminator,
definition_xmt,
attribute_definition: definition.id.clone(),
})
})
.collect::<Vec<_>>();
uses.sort_by(|first, second| first.id.cmp(&second.id));
uses
}
#[cfg(test)]
mod tests {
#![allow(unused_imports)]
use std::io::{Cursor, Write};
use flate2::write::ZlibEncoder;
use flate2::Compression;
use cadmpeg_ir::codec::{Codec, CodecEntry, Confidence, DecodeOptions};
use cadmpeg_ir::geometry::{
BlendCrossSection, BlendRadiusLaw, CurveGeometry, PcurveGeometry,
ProceduralCurveDefinition, ProceduralSurfaceDefinition, SurfaceGeometry,
};
use cadmpeg_ir::math::{Point2, Vector3};
use cadmpeg_ir::report::LossCategory;
use cadmpeg_ir::Exactness;
use crate::container;
use crate::parasolid::{self, StreamKind};
use crate::test_support::*;
use crate::NxCodec;
use super::*;
#[test]
fn topology_retains_entity_attribute_list_references() {
let mut stream = topology_partition_stream();
for (kind, attribute) in [(14, 41), (15, 42), (17, 43), (16, 44), (18, 45)] {
let at = stream
.windows(2)
.position(|window| window == [0, kind])
.expect("topology record");
put_ref(&mut stream, at + if kind == 17 { 4 } else { 8 }, attribute);
}
stream.extend_from_slice(&[0, 0x51]);
stream.extend_from_slice(&1u32.to_be_bytes());
stream.extend_from_slice(&41u16.to_be_bytes());
stream.extend_from_slice(&1u32.to_be_bytes());
stream.extend_from_slice(&0x21u16.to_be_bytes());
for reference in [4u16, 1, 1, 1, 1, 42] {
stream.extend_from_slice(&reference.to_be_bytes());
}
stream.extend_from_slice(&[0, 0x54]);
stream.extend_from_slice(&8u32.to_be_bytes());
stream.extend_from_slice(&42u16.to_be_bytes());
stream.extend_from_slice(b"deadbeef\0");
let graph = crate::topology::Graph::parse(&stream);
assert_eq!(
graph
.get(14, 4)
.expect("required invariant")
.face_fields()
.expect("required invariant")
.attributes,
41
);
assert_eq!(
graph
.get(15, 5)
.expect("required invariant")
.loop_fields()
.expect("required invariant")
.attributes,
42
);
assert_eq!(
graph
.get(17, 7)
.expect("required invariant")
.fin_fields()
.expect("required invariant")
.attributes,
43
);
assert_eq!(
graph
.get(16, 8)
.expect("required invariant")
.edge_fields()
.expect("required invariant")
.attributes,
44
);
assert_eq!(
graph
.get(18, 10)
.expect("required invariant")
.vertex_fields()
.expect("required invariant")
.attributes,
45
);
let result = NxCodec
.decode(
&mut Cursor::new(prt_with_partition(&stream)),
&DecodeOptions::default(),
)
.expect("required invariant");
let references = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidTopologyAttributeListReference>(
"parasolid_topology_attribute_list_references",
)
.expect("required invariant");
assert_eq!(references.len(), 5);
assert_eq!(references[0].topology_type, 14);
assert_eq!(references[0].topology_xmt, 4);
assert_eq!(references[0].attribute_list_xmt, 41);
assert!(references[0].attribute_list_record.is_some());
assert_eq!(result.ir.model.attributes.len(), 1);
assert_eq!(
result.ir.model.attributes[0].target,
cadmpeg_ir::attributes::AttributeTarget::Face(cadmpeg_ir::ids::FaceId(
"nx:s0:face#4".into()
))
);
assert_eq!(
result.ir.model.attributes[0].name,
"parasolid_type_84_reference_5"
);
assert_eq!(
result.ir.model.attributes[0].values,
[cadmpeg_ir::attributes::AttributeValue::String(
"deadbeef".into()
)]
);
}
#[test]
fn topology_attribute_class_uses_resolve_instance_discriminators_by_xmt() {
use super::{
ParasolidAttributeDefinition, ParasolidEntity51Record,
ParasolidTopologyAttributeListReference,
};
let definition = ParasolidAttributeDefinition {
id: "definition".into(),
stream_ordinal: 3,
xmt: 34,
name: "UG2/PMARK_ATTRIBUTE".into(),
field_count: 1,
field_record_xmt: 19,
field_record_references: [21, 22],
field_record_header_words: [0, 9000],
field_descriptor_prefix: [0; 26],
field_storage: None,
field_codes: vec![1],
inflated_offset: 100,
};
let entity = ParasolidEntity51Record {
id: "entity".into(),
stream_ordinal: 3,
xmt: 50,
flags: 1,
sequence: 7,
discriminator: 0x21,
references: vec![60, 61, 1, 62, 63, 64],
byte_len: 26,
inflated_offset: 200,
};
let reference = ParasolidTopologyAttributeListReference {
id: "topology-reference".into(),
stream_ordinal: 3,
topology_type: 14,
topology_xmt: 60,
attribute_list_xmt: 50,
attribute_list_record: Some(entity.id.clone()),
inflated_offset: 300,
};
let instance_uses = super::parasolid_attribute_class_uses(
std::slice::from_ref(&entity),
std::slice::from_ref(&definition),
);
assert_eq!(instance_uses.len(), 1);
assert_eq!(instance_uses[0].entity_51_record, entity.id);
assert_eq!(instance_uses[0].class_discriminator, 0x21);
assert_eq!(instance_uses[0].definition_xmt, 34);
assert_eq!(instance_uses[0].attribute_definition, definition.id);
let uses = super::parasolid_topology_attribute_class_uses(
std::slice::from_ref(&reference),
&instance_uses,
);
assert_eq!(uses.len(), 1);
assert_eq!(uses[0].class_discriminator, 0x21);
assert_eq!(uses[0].definition_xmt, 34);
assert_eq!(uses[0].attribute_definition, definition.id);
let mut invalid = entity;
invalid.discriminator = 0x20;
assert!(super::parasolid_attribute_class_uses(
std::slice::from_ref(&invalid),
std::slice::from_ref(&definition),
)
.is_empty());
assert!(super::parasolid_topology_attribute_class_uses(
&[reference],
&super::parasolid_attribute_class_uses(&[invalid], &[definition]),
)
.is_empty());
}
#[test]
fn parasolid_attribute_definition_requires_declared_printable_name_and_field_record() {
let mut bytes = vec![0xaa, 0x00, 0x4f, 0xff];
bytes.extend_from_slice(&16u32.to_be_bytes());
bytes.extend_from_slice(&0x012au16.to_be_bytes());
bytes.extend_from_slice(b"SDL/TYSA_DENSITY");
bytes.extend_from_slice(&[0x00, 0x50, 0x00, 0x00, 0x00, 0x01]);
bytes.extend_from_slice(&0x012bu16.to_be_bytes());
bytes.extend_from_slice(&0x0030u16.to_be_bytes());
bytes.extend_from_slice(&0x0031u16.to_be_bytes());
bytes.extend_from_slice(&[0x00, 0x00, 0x23, 0x28]);
let descriptor = [
0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
];
bytes.extend_from_slice(&descriptor);
bytes.push(1);
let definitions = crate::parasolid::attribute_definitions(&bytes);
assert_eq!(definitions.len(), 1);
assert_eq!(definitions[0].offset, 1);
assert_eq!(definitions[0].xmt, 0x12a);
assert_eq!(definitions[0].name, "SDL/TYSA_DENSITY");
assert_eq!(definitions[0].field_count, 1);
assert_eq!(definitions[0].field_record_xmt, 0x12b);
assert_eq!(definitions[0].field_record_references, [0x30, 0x31]);
assert_eq!(definitions[0].field_record_header_words, [0, 0x2328]);
assert_eq!(definitions[0].field_descriptor_prefix, descriptor);
assert_eq!(
super::parasolid_attribute_field_storage(&definitions[0].field_descriptor_prefix),
Some(super::ParasolidAttributeFieldStorage::Double)
);
assert_eq!(definitions[0].field_codes, [1]);
let truncated = &bytes[..bytes.len() - 1];
assert!(crate::parasolid::attribute_definitions(truncated).is_empty());
bytes[20] = 0;
assert!(crate::parasolid::attribute_definitions(&bytes).is_empty());
}
#[test]
fn decode_emits_offset_surface_construction() {
let stream = offset_surface_topology_partition_stream();
let mut cur = Cursor::new(prt_with_partition(&stream));
let result = NxCodec
.decode(&mut cur, &DecodeOptions::default())
.expect("required invariant");
let procedural = result
.ir
.model
.procedural_surfaces
.first()
.expect("offset surface");
let ProceduralSurfaceDefinition::Offset {
support,
distance,
u_sense,
v_sense,
extension_flags,
..
} = &procedural.definition
else {
panic!("offset definition");
};
assert_eq!(*distance, 2.5);
assert_eq!(*u_sense, Some(0));
assert_eq!(*v_sense, Some(0));
assert!(extension_flags.is_empty());
assert_ne!(procedural.surface, *support);
assert_eq!(result.ir.model.faces[0].surface, procedural.surface);
let records = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidOffsetSurfaceRecord>("parasolid_offset_surface_records")
.expect("required invariant");
assert_eq!(records.len(), 1);
assert_eq!(records[0].discriminator, 'V');
assert!(records[0].true_offset);
assert_eq!(records[0].support_xmt, 6);
assert_eq!(records[0].distance, 2.5);
let carrier = result
.ir
.model
.surfaces
.iter()
.find(|surface| surface.id == procedural.surface)
.expect("offset carrier");
assert_eq!(
carrier
.source_object
.as_ref()
.map(|source| &source.object_id),
Some(&records[0].id)
);
assert!(matches!(
&carrier.geometry,
SurfaceGeometry::Procedural { construction } if construction == &procedural.id
));
assert!(cadmpeg_ir::validate::validate(&result.ir, Vec::new()).is_ok());
}
#[test]
fn decode_resolves_surface_curve_to_its_basis_curve() {
let stream = surface_curve_topology_partition_stream();
let mut cur = Cursor::new(prt_with_partition(&stream));
let result = NxCodec
.decode(&mut cur, &DecodeOptions::default())
.expect("required invariant");
assert_eq!(result.ir.model.edges.len(), 1);
let records = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidSurfaceCurveRecord>("parasolid_surface_curve_records")
.expect("required invariant");
assert_eq!(records.len(), 1);
assert_eq!(records[0].surface_xmt, 6);
assert_eq!(records[0].pcurve_xmt, 9);
assert_eq!(records[0].original_curve_xmt, 9);
assert_eq!(records[0].tolerance_to_original, 0.000_01);
assert_eq!(
result.ir.model.edges[0].curve.as_ref(),
Some(&result.ir.model.curves[0].id)
);
assert!(cadmpeg_ir::validate::validate(&result.ir, Vec::new()).is_ok());
}
#[test]
fn decode_emits_rolling_ball_blend_surface() {
let stream = blend_surface_topology_partition_stream();
let mut cur = Cursor::new(prt_with_partition(&stream));
let result = NxCodec
.decode(&mut cur, &DecodeOptions::default())
.expect("required invariant");
let procedural = result
.ir
.model
.procedural_surfaces
.first()
.expect("blend surface");
let ProceduralSurfaceDefinition::Blend {
supports,
radius,
cross_section,
spine,
native,
} = &procedural.definition
else {
panic!("blend definition");
};
assert_eq!(*cross_section, BlendCrossSection::Circular);
assert_eq!(
*radius,
BlendRadiusLaw::Constant {
signed_radius: -3.0
}
);
assert_eq!(supports[0].as_ref().map(|side| side.reversed), Some(true));
assert_eq!(supports[1].as_ref().map(|side| side.reversed), Some(false));
assert!(spine.is_none());
assert!(native.is_none());
assert_eq!(result.ir.model.faces[0].surface, procedural.surface);
let records = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidBlendSurfaceRecord>("parasolid_blend_surface_records")
.expect("required invariant");
assert_eq!(records.len(), 1);
assert_eq!(records[0].support_xmts, [6, 6]);
assert_eq!(records[0].spine_xmt, 1);
assert_eq!(records[0].offsets, [-3.0, 3.0]);
assert_eq!(records[0].thumb_weights, [1.0, 1.0]);
let carrier = result
.ir
.model
.surfaces
.iter()
.find(|surface| surface.id == procedural.surface)
.expect("required invariant");
assert_eq!(
carrier
.source_object
.as_ref()
.map(|association| association.object_id.as_str()),
Some(records[0].id.as_str())
);
assert!(cadmpeg_ir::validate::validate(&result.ir, Vec::new()).is_ok());
}
#[test]
fn decode_preserves_intersection_curve_as_connected_carrier() {
let stream = intersection_curve_topology_partition_stream();
let mut cur = Cursor::new(prt_with_partition(&stream));
let result = NxCodec
.decode(&mut cur, &DecodeOptions::default())
.expect("required invariant");
let edge_curve = result.ir.model.edges[0].curve.as_ref().expect("edge curve");
let curve = result
.ir
.model
.curves
.iter()
.find(|curve| &curve.id == edge_curve)
.expect("intersection carrier");
assert!(matches!(curve.geometry, CurveGeometry::Unknown { .. }));
let records = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidIntersectionRecord>("parasolid_intersection_records")
.expect("required invariant");
assert_eq!(records.len(), 1);
assert!(!records[0].delta_twin);
assert_eq!(records[0].header_references[0], 1);
assert_eq!(records[0].construction_references, [6, 6, 1, 1, 1, 1]);
assert_eq!(
curve.source_object.as_ref().map(|source| &source.object_id),
Some(&records[0].id)
);
assert_eq!(result.ir.model.procedural_curves.len(), 1);
assert_eq!(result.ir.model.procedural_curves[0].curve, curve.id);
assert!(result.report.losses.iter().any(|loss| {
loss.category == LossCategory::Geometry
&& loss.message.starts_with("1 surface-intersection record(s)")
}));
assert!(cadmpeg_ir::validate::validate(&result.ir, Vec::new()).is_ok());
}
#[test]
fn decode_preserves_deltas_intersection_data_curve() {
let stream = deltas_intersection_curve_stream();
let mut cur = Cursor::new(prt_with_partition(&stream));
let result = NxCodec
.decode(&mut cur, &DecodeOptions::default())
.expect("required invariant");
assert_eq!(result.ir.model.procedural_curves.len(), 1);
let records = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidIntersectionRecord>("parasolid_intersection_records")
.expect("required invariant");
assert_eq!(records.len(), 1);
assert!(records[0].delta_twin);
assert_eq!(records[0].header_references[0], 1);
assert_eq!(records[0].construction_references, [6, 6, 1, 1, 1, 1]);
assert_eq!(
result.ir.model.edges[0].curve.as_ref(),
Some(&result.ir.model.procedural_curves[0].curve)
);
assert!(cadmpeg_ir::validate::validate(&result.ir, Vec::new()).is_ok());
}
#[test]
fn decode_emits_charted_surface_intersection_construction() {
let stream = charted_intersection_curve_topology_partition_stream();
let mut cur = Cursor::new(prt_with_partition(&stream));
let result = NxCodec
.decode(&mut cur, &DecodeOptions::default())
.expect("required invariant");
let terms = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidTermUseRecord>("parasolid_term_use_records")
.expect("required invariant");
assert_eq!(terms.len(), 2);
assert_eq!(terms[0].count, 1);
assert_eq!(terms[0].form, "L?");
assert_eq!(terms[0].point, [0.0, 0.0, 0.0]);
assert_eq!(terms[1].point, [10.0, 0.0, 0.0]);
assert!(terms
.iter()
.all(|term| matches!(term.framing, crate::intersection::TermUseFraming::Direct)));
let support_uv = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidSupportUvRecord>("parasolid_support_uv_records")
.expect("required invariant");
assert_eq!(support_uv.len(), 1);
assert_eq!(support_uv[0].count, 4);
assert_eq!(support_uv[0].marker, 2);
assert_eq!(support_uv[0].values, [0.0, 0.0, 0.01, 0.0]);
assert!(matches!(
support_uv[0].framing,
crate::intersection::SupportUvFraming::Direct
));
let charts = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidChartRecord>("parasolid_chart_records")
.expect("required invariant");
assert_eq!(charts.len(), 1);
assert_eq!(charts[0].count, 2);
assert_eq!(charts[0].base_parameter, 0.0);
assert_eq!(charts[0].base_scale, 1.0);
assert_eq!(charts[0].chart_count, 2);
assert_eq!(charts[0].chordal_error, 0.000_01);
assert_eq!(charts[0].angular_error, 0.001);
assert_eq!(charts[0].points, [[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]]);
assert!(matches!(
charts[0].point_layout,
crate::intersection::ChartPointLayout::Xyz3
));
let procedural = result
.ir
.model
.procedural_curves
.first()
.expect("intersection construction");
let curve = result
.ir
.model
.curves
.iter()
.find(|curve| curve.id == procedural.curve)
.expect("solved chart cache");
let CurveGeometry::Nurbs(nurbs) = &curve.geometry else {
panic!("charted NURBS cache");
};
assert_eq!(nurbs.degree, 1);
assert_eq!(nurbs.control_points[0].x, 0.0);
assert_eq!(nurbs.control_points[1].x, 10.0);
assert_eq!(procedural.cache_fit_tolerance, Some(0.01));
let cadmpeg_ir::geometry::ProceduralCurveDefinition::Intersection { context, .. } =
&procedural.definition
else {
panic!("typed surface intersection");
};
assert!(context.sides[0].surface.is_some());
assert!(context.sides[0].pcurve.is_some());
assert!(context.sides[1].surface.is_none());
assert_eq!(context.parameter_range, [0.0, 0.01]);
assert!(result.ir.model.coedges[0].pcurves.is_empty());
assert!(!result.report.losses.iter().any(|loss| {
loss.category == LossCategory::Geometry
&& loss.message.contains("surface-intersection record(s)")
}));
let validation = cadmpeg_ir::validate::validate(&result.ir, Vec::new());
assert!(validation.is_ok(), "findings: {:?}", validation.findings);
}
#[test]
fn decode_resolves_intersection_second_support_through_blend_bound() {
let stream = blend_bound_charted_intersection_curve_stream();
let mut cur = Cursor::new(prt_with_partition(&stream));
let result = NxCodec
.decode(&mut cur, &DecodeOptions::default())
.expect("required invariant");
let records = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidBlendBoundRecord>("parasolid_blend_bound_records")
.expect("required invariant");
assert_eq!(records.len(), 1);
assert_eq!(records[0].header_references, [1; 5]);
assert!(records[0].sense);
assert_eq!(records[0].boundary_index, 0);
assert_eq!(records[0].blend_surface_xmt, 13);
assert!(!records[0].escaped);
let cadmpeg_ir::geometry::ProceduralCurveDefinition::Intersection { context, .. } =
&result.ir.model.procedural_curves[0].definition
else {
panic!("typed intersection");
};
let second = context.sides[1].surface.as_ref().expect("bridged support");
assert_ne!(context.sides[0].surface.as_ref(), Some(second));
assert!(context.sides[1].pcurve.is_some());
}
#[test]
fn decode_resolves_trimmed_edge_to_its_basis_curve_and_range() {
let mut cur = Cursor::new(prt_with_partition(&trimmed_topology_partition_stream()));
let result = NxCodec
.decode(&mut cur, &DecodeOptions::default())
.expect("required invariant");
let edge = result.ir.model.edges.first().expect("edge");
assert_eq!(edge.curve.as_ref(), Some(&result.ir.model.curves[0].id));
assert_eq!(edge.param_range, Some([0.25, 0.75]));
let records = result
.ir
.native
.namespace("nx")
.expect("required invariant")
.arena_as::<super::ParasolidTrimmedCurveRecord>("parasolid_trimmed_curve_records")
.expect("required invariant");
assert_eq!(records.len(), 1);
assert_eq!(records[0].basis_xmt, 9);
assert_eq!(records[0].points, [[0.0; 3]; 2]);
assert_eq!(records[0].parameters, [0.000_25, 0.000_75]);
assert!(cadmpeg_ir::validate::validate(&result.ir, Vec::new()).is_ok());
}
}