use crate::kernel_header::RefWidth;
use crate::nurbs::blend::{
compact_rb_blend_spl_sur, cyl_spl_sur, full_rb_blend_spl_sur, rolling_ball_side,
var_blend_spl_sur, vertex_blend_spl_sur, SupportCaches,
};
use crate::nurbs::core::{curve_block, surface_block};
use crate::nurbs::pcurve::{decode_pcurve_block_with_end, pcurve_block_with_end};
use crate::nurbs::proc_curve::{
embedded_base_curve_resolving_refs, embedded_support, embedded_surface,
embedded_surface_with_ranges, optional_embedded_surface_with_bounds, optional_helix_revision,
EmbeddedSupport,
};
use crate::nurbs::reader::{normalized, take_native_ident, Nullable, LEN_TO_MM};
use crate::nurbs::toks::{self, Cur, SubtypeTable};
use crate::sab::Token;
use cadmpeg_core::decode::bounded_len;
use cadmpeg_ir::geometry::{
nurbs::{NurbsCurve, NurbsSurface},
pcurve::PcurveNurbs,
BlendCrossSection, CurveGeometry, RevisionCacheForm, RevisionSurfaceParameterization,
RollingBallSide, RollingBallSupportCurve, SolvedCurveGeometry, SurfaceGeometry,
VariableBlendCache,
};
use cadmpeg_ir::math::{Point3, Vector3};
use cadmpeg_ir::scalar::PositiveI64;
use cadmpeg_ir::units::UnitVector3;
use std::num::NonZeroI64;
macro_rules! propagate_resource {
($result:expr) => {
match $result {
Ok(value) => value,
Err(error) => return Some(Err(error)),
}
};
}
pub enum EmbeddedOffsetLayout {
Legacy {
u_sense: i64,
v_sense: i64,
extension: cadmpeg_ir::geometry::LegacyExtensionFlags,
},
Revision(Box<cadmpeg_ir::geometry::RevisionSurfaceForm<[bool; 4]>>),
}
pub struct DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition,
cache: ProceduralSurfaceCache,
}
pub(crate) enum ProceduralSurfaceCache {
Legacy(Option<f64>),
Revision,
}
impl DecodedProceduralSurface {
pub(super) fn legacy(
definition: DecodedProceduralSurfaceDefinition,
cache_fit_tolerance: Option<f64>,
) -> Self {
Self {
definition,
cache: ProceduralSurfaceCache::Legacy(cache_fit_tolerance),
}
}
pub(super) fn revision(definition: DecodedProceduralSurfaceDefinition) -> Self {
Self {
definition,
cache: ProceduralSurfaceCache::Revision,
}
}
pub fn definition(&self) -> &DecodedProceduralSurfaceDefinition {
&self.definition
}
#[cfg(test)]
pub(crate) fn legacy_cache_fit_tolerance(&self) -> Option<f64> {
match self.cache {
ProceduralSurfaceCache::Legacy(tolerance) => tolerance,
ProceduralSurfaceCache::Revision => None,
}
}
pub(crate) fn cache_fit_tolerance(&self) -> Option<f64> {
match self.cache {
ProceduralSurfaceCache::Legacy(tolerance) => tolerance,
ProceduralSurfaceCache::Revision => self.definition.revision_cache_fit_tolerance(),
}
}
pub(crate) fn into_parts(self) -> (DecodedProceduralSurfaceDefinition, ProceduralSurfaceCache) {
(self.definition, self.cache)
}
}
pub struct EmbeddedTSplineSurface {
pub parameter_ranges: [[f64; 2]; 2],
pub type_code: i64,
pub subtransform: EmbeddedTSplineSubtransform,
pub trailing_value: i64,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
pub revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
}
pub enum EmbeddedTSplineSubtransform {
Inline {
program: String,
separator: Option<bool>,
values: String,
},
Reference {
index: i64,
resolved: Option<cadmpeg_ir::geometry::InlineTSplineSubtransform>,
},
}
pub enum DecodedProceduralSurfaceDefinition {
Exact {
spline: cadmpeg_ir::geometry::ExactSpline,
},
Compound {
components: Vec<cadmpeg_ir::geometry::CompoundComponent<SurfaceGeometry>>,
},
SubSurface {
support: SurfaceGeometry,
parameter_ranges: [[f64; 2]; 2],
},
Taper {
support: SurfaceGeometry,
reference: NurbsCurve,
pcurve: Option<PcurveNurbs>,
parameter: f64,
taper: cadmpeg_ir::geometry::TaperSurfaceKind,
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Loft(EmbeddedLoft),
CompoundLoft(Box<EmbeddedCompoundLoft>),
RevisionCompoundLoft(Box<EmbeddedRevisionCompoundLoft>),
ScaledCompoundLoft(Box<EmbeddedScaledCompoundLoft>),
Skin(Box<EmbeddedSkinSurface>),
Law(Box<EmbeddedLawSurface>),
Net(Box<EmbeddedNetSurface>),
Sweep(Box<EmbeddedSweepSurface>),
TSpline(Box<EmbeddedTSplineSurface>),
Helix(Box<cadmpeg_ir::geometry::HelixSurfaceConstruction>),
Deformable(Box<EmbeddedDeformableSurface>),
G2Blend(Box<EmbeddedG2Blend>),
RevisionG2Blend(Box<EmbeddedRevisionG2Blend>),
Ruled {
first: NurbsCurve,
second: NurbsCurve,
},
Sum {
first: CurveGeometry,
second: CurveGeometry,
basepoint: Vector3,
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Revolution {
directrix: CurveGeometry,
axis_origin: Point3,
axis_direction: UnitVector3,
angular_interval: [f64; 2],
parameter_interval: [f64; 2],
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Offset {
support: SurfaceGeometry,
distance: f64,
layout: EmbeddedOffsetLayout,
},
Extrusion {
directrix: NurbsCurve,
parameter_interval: [f64; 2],
direction: Vector3,
native_position: Point3,
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Blend {
supports: Box<[Option<SurfaceGeometry>; 2]>,
spine: Option<NurbsCurve>,
radius_offsets: [f64; 2],
cross_section: BlendCrossSection,
native: Option<Box<EmbeddedRollingBall>>,
},
VariableBlend(Box<EmbeddedVariableBlend>),
VertexBlend(Box<EmbeddedVertexBlend>),
}
impl DecodedProceduralSurfaceDefinition {
fn revision_cache_fit_tolerance(&self) -> Option<f64> {
match self {
Self::Exact { spline } => match spline {
cadmpeg_ir::geometry::ExactSpline::Revision { form, .. } => form
.cache
.fit_tolerance()
.map(cadmpeg_ir::geometry::FitTolerance::get),
cadmpeg_ir::geometry::ExactSpline::Legacy { .. } => None,
},
Self::Taper { revision_form, .. }
| Self::Extrusion { revision_form, .. }
| Self::Revolution { revision_form, .. }
| Self::Sum { revision_form, .. } => revision_form
.as_ref()
.and_then(|form| form.cache.fit_tolerance())
.map(cadmpeg_ir::geometry::FitTolerance::get),
Self::Offset { layout, .. } => match layout {
EmbeddedOffsetLayout::Revision(form) => form
.cache
.fit_tolerance()
.map(cadmpeg_ir::geometry::FitTolerance::get),
EmbeddedOffsetLayout::Legacy { .. } => None,
},
Self::Loft(construction) => match &construction.layout {
EmbeddedLoftLayout::Revision(form, _) => form
.cache
.fit_tolerance()
.map(cadmpeg_ir::geometry::FitTolerance::get),
EmbeddedLoftLayout::Legacy { .. } => None,
},
Self::RevisionCompoundLoft(construction) => construction
.cache
.fit_tolerance()
.map(cadmpeg_ir::geometry::FitTolerance::get),
Self::RevisionG2Blend(construction) => construction
.cache
.fit_tolerance()
.map(cadmpeg_ir::geometry::FitTolerance::get),
Self::Sweep(construction) => match &construction.layout {
EmbeddedSweepSurfaceLayout::Revision { form, .. } => form
.cache
.fit_tolerance()
.map(cadmpeg_ir::geometry::FitTolerance::get),
EmbeddedSweepSurfaceLayout::Legacy { .. } => None,
},
Self::TSpline(construction) => construction
.revision_form
.as_ref()
.and_then(|form| form.cache.fit_tolerance())
.map(cadmpeg_ir::geometry::FitTolerance::get),
Self::Deformable(construction) => match &construction.layout {
EmbeddedDeformableSurfaceLayout::Revision(form) => form
.cache
.fit_tolerance()
.map(cadmpeg_ir::geometry::FitTolerance::get),
EmbeddedDeformableSurfaceLayout::Legacy { .. } => None,
},
Self::Blend { native, .. } => native
.as_ref()
.and_then(|construction| construction.cache.fit_tolerance())
.map(cadmpeg_ir::geometry::FitTolerance::get),
Self::VariableBlend(construction) => construction
.cache
.fit_tolerance()
.map(cadmpeg_ir::geometry::FitTolerance::get),
Self::Compound { .. }
| Self::SubSurface { .. }
| Self::CompoundLoft(_)
| Self::ScaledCompoundLoft(_)
| Self::Skin(_)
| Self::Law(_)
| Self::Net(_)
| Self::Helix(_)
| Self::G2Blend(_)
| Self::Ruled { .. }
| Self::VertexBlend(_) => None,
}
}
}
pub struct EmbeddedRevisionG2Blend {
pub revision: PositiveI64,
pub leading_parameters: [f64; 2],
pub sides: Box<[RollingBallSide<SurfaceGeometry, CurveGeometry, PcurveNurbs>; 2]>,
pub center: CurveGeometry,
pub center_range: [Option<f64>; 2],
pub radii: [f64; 2],
pub radius_selector: cadmpeg_ir::geometry::RollingBallRadiusSelector<PositiveI64>,
pub u_range: [Option<f64>; 2],
pub v_range: [Option<f64>; 2],
pub shape_prefix: i64,
pub shape_parameter: f64,
pub shape_length: f64,
pub shape_tail: i64,
pub cache: RevisionCacheForm,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub tail_extensions: [i64; 3],
}
pub struct EmbeddedRollingBallThirdSide {
pub label: String,
pub surface: SurfaceGeometry,
pub curve: NurbsCurve,
pub pcurve: Option<PcurveNurbs>,
pub direction: Vector3,
pub secondary_pcurve: Option<PcurveNurbs>,
pub extension: i64,
pub tertiary_pcurve: Option<PcurveNurbs>,
pub flag: bool,
}
pub struct EmbeddedVariableBlend {
pub subtype: cadmpeg_ir::geometry::VariableBlendSurfaceSubtype,
pub revision: PositiveI64,
pub sides: Box<[RollingBallSide<SurfaceGeometry, CurveGeometry, PcurveNurbs>; 2]>,
pub slice: CurveGeometry,
pub slice_range: [Option<f64>; 2],
pub offsets: [f64; 2],
pub radii: cadmpeg_ir::geometry::VariableBlendRadii,
pub cross_section: Option<cadmpeg_ir::geometry::VariableBlendCrossSection>,
pub u_range: [f64; 2],
pub v_lower: Option<f64>,
pub shape_parameter: f64,
pub shape_length: f64,
pub shape_tail: i64,
pub cache: VariableBlendCache,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub tail_extensions: [i64; 3],
pub secondary_curve: Option<RollingBallSupportCurve<CurveGeometry>>,
pub convexity: cadmpeg_ir::geometry::VariableBlendConvexity,
pub render_mode: cadmpeg_ir::geometry::VariableBlendRenderMode,
pub post_range: [Option<f64>; 2],
pub post_curve: Option<NurbsCurve>,
pub post_pcurve: Option<PcurveNurbs>,
}
pub enum EmbeddedVertexBlendBoundaryGeometry {
Circle {
curve: CurveGeometry,
curve_endpoints: [Option<f64>; 2],
twists: cadmpeg_ir::geometry::VertexBlendTwists,
parameters: [f64; 2],
sense: bool,
},
Degenerate {
location: Point3,
normals: [Vector3; 2],
},
Pcurve {
surface: SurfaceGeometry,
support_bounds: [Option<f64>; 4],
pcurve: Option<PcurveNurbs>,
sense: bool,
fit_tolerance: cadmpeg_ir::geometry::FitTolerance,
},
Plane {
normal: Vector3,
parameters: [f64; 2],
curve: CurveGeometry,
curve_endpoints: [Option<f64>; 2],
},
}
pub struct EmbeddedVertexBlendBoundary {
pub boundary_type: bool,
pub magic: Vector3,
pub u_smoothing: bool,
pub v_smoothing: bool,
pub fullness: f64,
pub geometry: EmbeddedVertexBlendBoundaryGeometry,
}
pub struct EmbeddedVertexBlend {
pub revision: Option<PositiveI64>,
pub boundaries: Vec<EmbeddedVertexBlendBoundary>,
pub grid_size: i64,
pub fit_tolerance: cadmpeg_ir::geometry::FitTolerance,
}
pub struct EmbeddedRollingBall {
pub revision: PositiveI64,
pub sides: Box<[RollingBallSide<SurfaceGeometry, CurveGeometry, PcurveNurbs>; 2]>,
pub slice: CurveGeometry,
pub slice_range: [Option<f64>; 2],
pub offsets: [f64; 2],
pub radius_selector: Option<f64>,
pub u_range: [Option<f64>; 2],
pub v_range: [Option<f64>; 2],
pub shape_prefix: i64,
pub parameters: [f64; 2],
pub tail: i64,
pub cache: RevisionCacheForm,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub third: Option<Box<EmbeddedRollingBallThirdSide>>,
pub tail_extensions: [i64; 3],
}
pub struct EmbeddedG2Side {
pub label: String,
pub surface: SurfaceGeometry,
pub curve: NurbsCurve,
pub pcurves: [Option<PcurveNurbs>; 2],
pub direction: Vector3,
}
pub enum EmbeddedG2FirstShape {
Full(Option<(NurbsSurface, cadmpeg_ir::geometry::FitTolerance)>),
None {
coefficients: [f64; 9],
tolerance: cadmpeg_ir::geometry::FitTolerance,
extension: Option<cadmpeg_ir::geometry::LoftBridgeToken>,
pcurve: Option<PcurveNurbs>,
},
}
pub struct EmbeddedG2Blend {
pub first: EmbeddedG2Side,
pub singularity: i64,
pub first_shape: EmbeddedG2FirstShape,
pub second: EmbeddedG2Side,
pub second_exact_surface: NurbsSurface,
pub center_curve: NurbsCurve,
pub center_parameters: [f64; 2],
pub center_flag: i64,
pub parameter_ranges: [[f64; 2]; 2],
pub trailing_parameters: [f64; 4],
pub discontinuities: [Vec<f64>; 3],
}
pub(super) fn decode_nullable_embedded_pcurve(
bytes: &[u8],
position: &mut usize,
int_width: RefWidth,
) -> Option<Nullable<PcurveNurbs>> {
let saved = *position;
if take_native_ident(bytes, position).as_deref() == Some("nullbs") {
return Some(Nullable::Null);
}
*position = saved;
let (pcurve, end) = decode_pcurve_block_with_end(bytes, *position, int_width)?;
*position = end;
Some(Nullable::Value(pcurve))
}
pub(super) fn nullable_embedded_pcurve(cur: &mut Cur<'_>) -> Option<Nullable<PcurveNurbs>> {
let saved = cur.pos();
if cur.take_ident() == Some("nullbs") {
return Some(Nullable::Null);
}
cur.set_pos(saved);
let (pcurve, end) = pcurve_block_with_end(cur.toks(), cur.pos())?;
cur.set_pos(end);
Some(Nullable::Value(pcurve))
}
pub(super) fn g2_side(cur: &mut Cur<'_>) -> Option<EmbeddedG2Side> {
let label = cur.take_str()?.to_owned();
let EmbeddedSupport { surface, chart, .. } = embedded_support(cur)?;
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
let mut first = nullable_embedded_pcurve(cur)?.value();
if let Some(pcurve) = &mut first {
chart.normalize(pcurve)?;
}
let direction = cur.take_vector3()?;
let second = nullable_embedded_pcurve(cur)?.value();
Some(EmbeddedG2Side {
label,
surface,
curve,
pcurves: [first, second],
direction: Vector3::new(direction[0], direction[1], direction[2]),
})
}
fn bridge_token(cur: &mut Cur<'_>) -> Option<cadmpeg_ir::geometry::LoftBridgeToken> {
use cadmpeg_ir::geometry::LoftBridgeToken;
match cur.peek()? {
Token::True | Token::False => Some(LoftBridgeToken::Boolean(cur.take_bool()?)),
Token::Long(_) => Some(LoftBridgeToken::Integer(cur.take_long()?)),
Token::Double(_) => Some(LoftBridgeToken::Double(cur.take_f64()?)),
Token::Enum(_) => Some(LoftBridgeToken::Enum(cur.take_enum()?)),
Token::Str(_) => Some(LoftBridgeToken::Text(cur.take_str()?.to_owned())),
_ => None,
}
}
fn optional_trailing_cache_tolerance(cur: &mut Cur<'_>) -> Option<Nullable<f64>> {
if cur.at_scope_end() {
Some(Nullable::Null)
} else {
let tolerance = cur.take_f64()? * LEN_TO_MM;
cur.at_scope_end().then_some(Nullable::Value(tolerance))
}
}
fn g2_blend_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let names = ["g2_blend_spl_sur", "g2blnsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "g2_blend_spl_sur").then_some(())?;
let revision = PositiveI64::new(cur.take_long()?)?;
let leading_parameters = [cur.take_f64()?, cur.take_f64()?];
let first = match rolling_ball_side(ctx, &mut cur, resolver)? {
Ok(side) => side,
Err(limit) => return Some(Err(limit)),
};
let second = match rolling_ball_side(ctx, &mut cur, resolver)? {
Ok(side) => side,
Err(limit) => return Some(Err(limit)),
};
let sides = Box::new([first, second]);
let table = resolver?;
let center = embedded_base_curve_resolving_refs(&mut cur, table)?;
let center_range = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let radii = [cur.take_f64()? * LEN_TO_MM, cur.take_f64()? * LEN_TO_MM];
let radius_selector = match cur.take_enum()? {
-1 => cadmpeg_ir::geometry::RollingBallRadiusSelector::None {},
value => cadmpeg_ir::geometry::RollingBallRadiusSelector::Value {
value: PositiveI64::new(value)?,
},
};
let u_range = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let v_range = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let shape_prefix = cur.take_long()?;
let shape_parameter = cur.take_f64()?;
let shape_length = cur.take_f64()? * LEN_TO_MM;
let shape_tail = cur.take_long()?;
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let tail_extensions = [cur.take_long()?, cur.take_long()?, cur.take_long()?];
cur.at_scope_end().then_some(())?;
return Some(Ok(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::RevisionG2Blend(Box::new(
EmbeddedRevisionG2Blend {
revision,
leading_parameters,
sides,
center: CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(center)),
center_range,
radii,
radius_selector,
u_range,
v_range,
shape_prefix,
shape_parameter,
shape_length,
shape_tail,
cache: cache.into_form()?,
discontinuities,
tail_flag,
tail_extensions,
},
)),
)));
}
let first = g2_side(&mut cur)?;
let singularity = cur.take_enum()?;
let first_shape = if cur.peek().is_some_and(Token::is_payload_ident) {
let saved = cur.pos();
if cur.take_ident() == Some("nullbs") {
EmbeddedG2FirstShape::Full(None)
} else {
cur.set_pos(saved);
let (surface, surface_end) = surface_block(span, cur.pos())?;
cur.set_pos(surface_end);
EmbeddedG2FirstShape::Full(Some((
surface,
cadmpeg_ir::geometry::FitTolerance::try_new(cur.take_f64()? * LEN_TO_MM).ok()?,
)))
}
} else {
let mut coefficients = [0.0; 9];
for coefficient in &mut coefficients {
*coefficient = cur.take_f64()?;
}
let tolerance =
cadmpeg_ir::geometry::FitTolerance::try_new(cur.take_f64()? * LEN_TO_MM).ok()?;
let extension = if matches!(cur.peek(), Some(token)
if matches!(token, Token::Str(_)) || token.is_payload_ident())
{
None
} else {
bridge_token(&mut cur)
};
let pcurve = nullable_embedded_pcurve(&mut cur)?.value();
EmbeddedG2FirstShape::None {
coefficients,
tolerance,
extension,
pcurve,
}
};
let second = g2_side(&mut cur)?;
let (second_exact, second_exact_end) = surface_block(span, cur.pos())?;
cur.set_pos(second_exact_end);
let (center, center_end) = curve_block(span, cur.pos())?;
cur.set_pos(center_end);
let center_parameters = [cur.take_f64()?, cur.take_f64()?];
let center_flag = cur.take_long()?;
let parameter_ranges = [
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
];
let mut trailing_parameters = [0.0; 4];
for parameter in &mut trailing_parameters {
*parameter = cur.take_f64()?;
}
let (_, cache_end) = surface_block(span, cur.pos())?;
let cache_fit_tolerance = match span.get(cache_end) {
Some(Token::Double(value)) => Some(*value * LEN_TO_MM),
_ => None,
};
cur.set_pos(cache_end + usize::from(cache_fit_tolerance.is_some()));
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
Some(Ok(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::G2Blend(Box::new(EmbeddedG2Blend {
first,
singularity,
first_shape,
second,
second_exact_surface: second_exact,
center_curve: center,
center_parameters,
center_flag,
parameter_ranges,
trailing_parameters,
discontinuities,
})),
cache_fit_tolerance,
)))
}
pub struct ClassicLoftProfileData {
pub type_code: i64,
pub surface: SurfaceGeometry,
pub pcurve: Option<PcurveNurbs>,
pub first_flag: bool,
pub asm_extension: i64,
pub subdata: cadmpeg_ir::geometry::LoftSubdata,
pub direction: Option<Vector3>,
}
pub enum LoftProfileData {
Classic(ClassicLoftProfileData),
RevisionSupport {
endpoints: [Option<f64>; 2],
type_code: std::num::NonZeroI64,
surface: Option<SurfaceGeometry>,
support_bounds: [Option<f64>; 4],
pcurve: Option<PcurveNurbs>,
first_flag: bool,
asm_extension: Option<i64>,
subdata: cadmpeg_ir::geometry::LoftSubdata,
direction: Option<Vector3>,
},
RevisionPcurvePair {
endpoints: [Option<f64>; 2],
pcurve: Option<PcurveNurbs>,
secondary_pcurve: Option<PcurveNurbs>,
asm_extension: Option<i64>,
subdata: cadmpeg_ir::geometry::LoftSubdata,
direction: Option<Vector3>,
},
}
impl LoftProfileData {
pub fn endpoints(&self) -> Option<[Option<f64>; 2]> {
match self {
Self::Classic(_) => None,
Self::RevisionSupport { endpoints, .. }
| Self::RevisionPcurvePair { endpoints, .. } => Some(*endpoints),
}
}
}
pub struct EmbeddedLoftProfileMember<D = LoftProfileData> {
pub curve: NurbsCurve,
pub data: D,
}
pub struct EmbeddedRevisionLoftPathCurve {
pub geometry: NurbsCurve,
pub endpoints: [Option<f64>; 2],
}
pub enum EmbeddedLoftPathLayout {
Legacy(NurbsCurve),
Revision(Option<EmbeddedRevisionLoftPathCurve>),
}
pub struct EmbeddedLoftPath {
pub layout: EmbeddedLoftPathLayout,
pub auxiliaries: Vec<NurbsCurve>,
pub flag: i64,
}
pub struct EmbeddedRevisionCompoundLoft {
pub revision: PositiveI64,
pub cache: RevisionCacheForm,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub base_profile: Vec<EmbeddedLoftProfileMember>,
pub base_path: EmbeddedLoftPath,
pub entries: Vec<EmbeddedLoftSectionEntry>,
pub flags: [bool; 2],
pub kind_flags: [bool; 2],
pub direction: EmbeddedCompoundLoftDirection,
pub tail: cadmpeg_ir::geometry::RevisionCompoundLoftTail<NurbsCurve>,
}
pub struct EmbeddedLoftSectionEntry {
pub parameter: f64,
pub profile: Vec<EmbeddedLoftProfileMember>,
pub path: EmbeddedLoftPath,
}
pub struct EmbeddedLoft {
pub sections: [Vec<EmbeddedLoftSectionEntry>; 2],
pub layout: EmbeddedLoftLayout,
}
pub enum EmbeddedLoftLayout {
Legacy {
ranges: [[f64; 2]; 2],
closures: [i64; 2],
singularities: [i64; 2],
mode: i64,
bridge: Vec<cadmpeg_ir::geometry::LoftBridgeToken>,
},
Revision(
Box<cadmpeg_ir::geometry::LoftRevisionForm>,
[[Option<f64>; 2]; 2],
),
}
pub struct EmbeddedCompoundLoftScale {
pub members: Vec<EmbeddedLoftProfileMember<ClassicLoftProfileData>>,
pub path: NurbsCurve,
pub auxiliaries: Vec<NurbsCurve>,
pub tail: [i64; 2],
}
pub enum EmbeddedCompoundLoftDirection {
Vector(Vector3),
Curve {
selector: NonZeroI64,
curve: NurbsCurve,
},
}
pub enum EmbeddedCompoundLoftTail {
Six {
flags: [bool; 2],
scale: Box<EmbeddedCompoundLoftScale>,
selector: i64,
direction: Vector3,
parameter_range: [f64; 2],
curve: NurbsCurve,
},
Seven {
first_flag: bool,
first_scale: Option<Box<EmbeddedCompoundLoftScale>>,
second_flag: bool,
second_scale: Box<EmbeddedCompoundLoftScale>,
selector: i64,
direction: Vector3,
trailing_flags: [bool; 2],
},
Zero {
flags: [bool; 2],
direction: EmbeddedCompoundLoftDirection,
trailing_flags: [bool; 2],
},
}
pub struct EmbeddedCompoundLoft {
pub scales: Box<[Option<EmbeddedCompoundLoftScale>; 4]>,
pub fifth_scale: Option<Box<EmbeddedCompoundLoftScale>>,
pub flags: [bool; 2],
pub tail: EmbeddedCompoundLoftTail,
}
pub enum EmbeddedScaledCompoundLoftShape {
Full,
None {
parameter_ranges: [[f64; 2]; 2],
parameters: [Vec<f64>; 2],
},
}
pub enum EmbeddedScaledCompoundLoftBranch {
ExtendedVector {
first_scale: Option<Box<EmbeddedCompoundLoftScale>>,
second_scale: Box<EmbeddedCompoundLoftScale>,
selector: i64,
direction: Vector3,
},
ExtendedCurve {
scale: Option<Box<EmbeddedCompoundLoftScale>>,
flag: bool,
singularity: i64,
curve: NurbsCurve,
},
Direct {
flag: bool,
direction: EmbeddedCompoundLoftDirection,
},
}
pub struct EmbeddedScaledCompoundLoft {
pub singularity: i64,
pub shape: EmbeddedScaledCompoundLoftShape,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
pub scales: Box<[Option<EmbeddedCompoundLoftScale>; 3]>,
pub flags: [bool; 2],
pub selector: i64,
pub branch: EmbeddedScaledCompoundLoftBranch,
pub trailing_flags: [bool; 2],
pub tail_kind: i64,
pub tail_directions: [Vector3; 2],
pub tail_singularity: i64,
pub tail_curve: NurbsCurve,
}
pub enum EmbeddedLawExpression {
Null,
Text(cadmpeg_core::text::NonBlankString),
Integer(i64),
Double(f64),
Point(Point3),
Vector(Vector3),
Transform {
scalars: [f64; 13],
enums: [i64; 3],
},
TransformVec {
vectors: [Vector3; 4],
scale: f64,
flags: [bool; 3],
},
Edge {
curve: NurbsCurve,
endpoints: Option<[Option<f64>; 2]>,
parameters: [f64; 2],
},
Spline {
native_id: i64,
knots: Vec<f64>,
controls: Vec<f64>,
point: Point3,
},
Algebraic {
operator: String,
operands: Vec<EmbeddedLawExpression>,
},
}
pub enum EmbeddedLawFormula {
Null,
Named {
name: cadmpeg_core::text::NonBlankString,
variables: Vec<EmbeddedLawExpression>,
},
}
impl EmbeddedLawFormula {
#[cfg(test)]
pub(super) fn name(&self) -> &str {
match self {
Self::Null => "null_law",
Self::Named { name, .. } => name.as_str(),
}
}
#[cfg(test)]
fn variables(&self) -> &[EmbeddedLawExpression] {
match self {
Self::Null => &[],
Self::Named { variables, .. } => variables,
}
}
}
pub struct EmbeddedLawSurface {
pub parameter_ranges: Option<[[f64; 2]; 2]>,
pub primary: EmbeddedLawFormula,
pub additional: Vec<EmbeddedLawFormula>,
pub tail: cadmpeg_ir::geometry::LawSurfaceTail,
pub discontinuities: [Vec<f64>; 6],
}
pub enum EmbeddedSkinSurfaceLayout {
Profiles {
profiles: Vec<EmbeddedLoftProfileMember<ClassicLoftProfileData>>,
path: NurbsCurve,
tail: [i64; 2],
},
Compact {
inner_count: i64,
curve: NurbsCurve,
subdata: cadmpeg_ir::geometry::LoftSubdata,
first_tail: i64,
secondary_curve: NurbsCurve,
second_tail: i64,
},
}
pub struct EmbeddedSkinSurface {
pub surface_boolean: i64,
pub surface_normal: i64,
pub surface_direction: i64,
pub count: i64,
pub parameter: f64,
pub layout: EmbeddedSkinSurfaceLayout,
pub direction: Vector3,
pub trailing_parameter: f64,
pub formula: EmbeddedLawFormula,
pub parameter_curve: NurbsCurve,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
}
pub struct EmbeddedNetSurface {
pub sections: Box<[Vec<EmbeddedLoftSectionEntry>; 2]>,
pub frame_parameters: [f64; 12],
pub flag: i64,
pub directions: [Vector3; 4],
pub formulas: Box<[EmbeddedLawFormula; 4]>,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
}
pub struct SweepProfile {
pub profile: NurbsCurve,
pub mode: i64,
pub profile_range: [f64; 2],
pub profile_frame: Option<(Point3, Vector3)>,
pub origin: Point3,
pub directions: [Vector3; 3],
pub path: NurbsCurve,
pub path_range: [f64; 2],
pub path_parameter: f64,
}
pub enum LegacySweepLayout {
ProfileFirst {
profile: Box<NurbsCurve>,
spine: Box<NurbsCurve>,
secondary_kind: i64,
directions: [Vector3; 5],
origin: Point3,
parameters: [f64; 4],
formulas: Box<[EmbeddedLawFormula; 3]>,
},
Sweep {
profile: Box<SweepProfile>,
tail: SweepTail,
},
}
pub enum SweepTail {
LawOrFormula(SweepLawOrFormula),
Guide {
trajectory_flag: bool,
guide_flags: [bool; 2],
guide_curve: NurbsCurve,
guide_range: [f64; 2],
guide_modes: [i64; 2],
guide_parameters: [f64; 6],
trailing_flags: [bool; 3],
},
Surface {
trajectory_flag: bool,
singularity: i64,
support_surface: SurfaceGeometry,
auxiliary_curve: Option<NurbsCurve>,
support_flag: bool,
legacy_flag: Option<bool>,
},
}
pub enum SweepLawOrFormula {
Formula {
trajectory_flag: bool,
formula_flag: bool,
formula: EmbeddedLawFormula,
trailing_flag: bool,
},
Law {
first_law: Box<EmbeddedLawExpression>,
first_mode: i64,
first_range: [f64; 2],
law_direction: Vector3,
path_mode: i64,
path_flag: bool,
second_law_flag: bool,
second_law: Box<EmbeddedLawExpression>,
formula_mode: i64,
formula: EmbeddedLawFormula,
trailing_flag: bool,
},
}
pub enum EmbeddedSweepSurfaceLayout {
Legacy {
primary_kind: i64,
layout: LegacySweepLayout,
},
Revision {
form: cadmpeg_ir::geometry::SweepRevisionForm,
profile: Box<SweepProfile>,
tail: SweepLawOrFormula,
},
}
pub struct EmbeddedSweepSurface {
pub layout: EmbeddedSweepSurfaceLayout,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
}
pub struct EmbeddedDeformableSurface {
pub support: SurfaceGeometry,
pub layout: EmbeddedDeformableSurfaceLayout,
pub data: EmbeddedDeformableSurfaceData,
}
pub enum EmbeddedDeformableSurfaceLayout {
Legacy {
discontinuities: [Vec<f64>; 6],
discontinuity_flag: bool,
},
Revision(Box<cadmpeg_ir::geometry::RevisionSurfaceForm>),
}
pub enum EmbeddedDeformableSurfaceData {
Resolved(cadmpeg_ir::geometry::DeformableSurfaceData),
SurfaceCurve {
surface: SurfaceGeometry,
native_id: i64,
flag: bool,
first_parameter: f64,
selector: i64,
second_parameter: f64,
curve: NurbsCurve,
vectors: [Vector3; 4],
frame_parameter: f64,
flags: [bool; 3],
parameter_triples: Vec<[f64; 3]>,
},
Full {
leading_vectors: [Vector3; 4],
leading_parameter: f64,
leading_flags: [bool; 3],
selector: i64,
surface: SurfaceGeometry,
native_id: i64,
flag: bool,
first_parameter: f64,
version_value: Option<i64>,
second_parameter: f64,
curve: NurbsCurve,
frames: Box<[cadmpeg_ir::geometry::DeformableVectorFrame; 2]>,
trailing_value: i64,
},
}
fn compound_loft_scale(cur: &mut Cur<'_>) -> Option<Nullable<EmbeddedCompoundLoftScale>> {
if matches!(cur.peek(), Some(Token::True | Token::False)) {
return Some(Nullable::Null);
}
let count = usize::try_from(cur.take_long()?).ok()?;
if count > 100_000 {
return None;
}
let mut members = Vec::new();
for _ in 0..count {
let type_code = cur.take_long()?;
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
let data = loft_profile_data(cur, type_code)?;
members.push(EmbeddedLoftProfileMember { curve, data });
}
let (path, path_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(path_end);
let auxiliary_count = usize::try_from(cur.take_long()?).ok()?;
if auxiliary_count > 100_000 {
return None;
}
let mut auxiliaries = Vec::new();
for _ in 0..auxiliary_count {
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
auxiliaries.push(curve);
}
let tail = [cur.take_long()?, cur.take_long()?];
Some(Nullable::Value(EmbeddedCompoundLoftScale {
members,
path,
auxiliaries,
tail,
}))
}
pub(super) fn ellipse_to_nurbs(
center: [f64; 3],
normal: [f64; 3],
major: [f64; 3],
ratio: f64,
) -> Option<NurbsCurve> {
let length = major[0].hypot(major[1]).hypot(major[2]);
(length.is_finite() && length > 0.0).then_some(())?;
let minor_direction = [
normal[1] * major[2] - normal[2] * major[1],
normal[2] * major[0] - normal[0] * major[2],
normal[0] * major[1] - normal[1] * major[0],
];
let minor_length = minor_direction[0]
.hypot(minor_direction[1])
.hypot(minor_direction[2]);
(minor_length.is_finite() && minor_length > 0.0).then_some(())?;
let minor_scale = ratio * length / minor_length;
let minor = [
minor_direction[0] * minor_scale,
minor_direction[1] * minor_scale,
minor_direction[2] * minor_scale,
];
let at = |mj: f64, mn: f64| {
Point3::new(
(center[0] + mj * major[0] + mn * minor[0]) * LEN_TO_MM,
(center[1] + mj * major[1] + mn * minor[1]) * LEN_TO_MM,
(center[2] + mj * major[2] + mn * minor[2]) * LEN_TO_MM,
)
};
let corner = cadmpeg_ir::scalar::NonZeroReal::FRAC_1_SQRT_2;
let full = cadmpeg_ir::scalar::NonZeroReal::ONE;
let pole = |point, weight| cadmpeg_ir::geometry::nurbs::WeightedPole3 { point, weight };
NurbsCurve::new(
2,
vec![
0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0,
],
cadmpeg_ir::geometry::nurbs::NurbsPoles3::Rational {
points: vec![
pole(at(1.0, 0.0), full),
pole(at(1.0, 1.0), corner),
pole(at(0.0, 1.0), full),
pole(at(-1.0, 1.0), corner),
pole(at(-1.0, 0.0), full),
pole(at(-1.0, -1.0), corner),
pole(at(0.0, -1.0), full),
pole(at(1.0, -1.0), corner),
pole(at(1.0, 0.0), full),
],
},
false,
)
.ok()
}
const LOFT_ASM_EXTENSION_ABSENT_THROUGH: u32 = 22600;
fn revision_loft_carries_asm_extension(table: &SubtypeTable) -> bool {
table
.save_format_version()
.is_none_or(|version| version > LOFT_ASM_EXTENSION_ABSENT_THROUGH)
}
fn revision_loft_profile_data(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
table: &SubtypeTable,
type_code: i64,
asm_extension_present: bool,
endpoints: [Option<f64>; 2],
) -> Option<Result<LoftProfileData, cadmpeg_core::CodecError>> {
let tail = |cur: &mut Cur<'_>| {
let asm_extension = if asm_extension_present {
Some(cur.take_long()?)
} else {
None
};
let subdata = loft_subdata_form(cur, true)?;
let direction = if cur.take_bool()? {
let value = cur.take_vector3()?;
Some(Vector3::new(value[0], value[1], value[2]))
} else {
None
};
Some(Ok((asm_extension, subdata, direction)))
};
match std::num::NonZeroI64::new(type_code) {
Some(type_code) => {
let crate::nurbs::proc_curve::EmbeddedSurfaceWithBounds {
surface,
chart,
bounds: support_bounds,
} = match optional_embedded_surface_with_bounds(ctx, cur, table)? {
Ok(surface) => surface,
Err(error) => return Some(Err(error)),
};
let mut pcurve = nullable_embedded_pcurve(cur)?.value();
if let Some(pcurve) = &mut pcurve {
chart.normalize(pcurve)?;
}
let first_flag = cur.take_bool()?;
let (asm_extension, subdata, direction) = propagate_resource!(tail(cur)?);
Some(Ok(LoftProfileData::RevisionSupport {
endpoints,
type_code,
surface: surface.into_surface(),
support_bounds,
pcurve,
first_flag,
asm_extension,
subdata,
direction,
}))
}
None => {
let pcurve = nullable_embedded_pcurve(cur)?.value();
let secondary_pcurve = nullable_embedded_pcurve(cur)?.value();
let (asm_extension, subdata, direction) = propagate_resource!(tail(cur)?);
Some(Ok(LoftProfileData::RevisionPcurvePair {
endpoints,
pcurve,
secondary_pcurve,
asm_extension,
subdata,
direction,
}))
}
}
}
fn revision_loft_section(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
table: &SubtypeTable,
asm_extension_present: bool,
) -> Option<Result<Vec<EmbeddedLoftSectionEntry>, cadmpeg_core::CodecError>> {
let count = usize::try_from(cur.take_long()?).ok()?;
let count = bounded_len(
cadmpeg_core::decode::u64_from_index(count),
1,
cur.rest().len(),
)?;
let mut entries = Vec::new();
for _ in 0..count {
let parameter = cur.take_f64()?;
let member_count = usize::try_from(cur.take_long()?).ok()?;
let member_count = bounded_len(
cadmpeg_core::decode::u64_from_index(member_count),
1,
cur.rest().len(),
)?;
let mut profile = Vec::new();
for _ in 0..member_count {
let type_code = cur.take_long()?;
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let data = match revision_loft_profile_data(
ctx,
cur,
table,
type_code,
asm_extension_present,
endpoints,
)? {
Ok(data) => data,
Err(error) => return Some(Err(error)),
};
profile.push(EmbeddedLoftProfileMember { curve, data });
}
let saved = cur.pos();
let path_curve = if cur.take_ident() == Some("null_curve") {
None
} else {
cur.set_pos(saved);
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
Some(EmbeddedRevisionLoftPathCurve {
geometry: curve,
endpoints,
})
};
let auxiliary_count = usize::try_from(cur.take_long()?).ok()?;
let auxiliary_count = bounded_len(
cadmpeg_core::decode::u64_from_index(auxiliary_count),
1,
cur.rest().len(),
)?;
let mut auxiliaries = Vec::new();
for _ in 0..auxiliary_count {
let (auxiliary, auxiliary_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(auxiliary_end);
auxiliaries.push(auxiliary);
}
let flag = cur.take_long()?;
entries.push(EmbeddedLoftSectionEntry {
parameter,
profile,
path: EmbeddedLoftPath {
layout: EmbeddedLoftPathLayout::Revision(path_curve),
auxiliaries,
flag,
},
});
}
Some(Ok(entries))
}
fn loft_subdata(cur: &mut Cur<'_>) -> Option<cadmpeg_ir::geometry::LoftSubdata> {
loft_subdata_form(cur, false)
}
fn loft_subdata_form(
cur: &mut Cur<'_>,
revision: bool,
) -> Option<cadmpeg_ir::geometry::LoftSubdata> {
use cadmpeg_ir::geometry::{LoftSubdata, LoftSubdataRow};
let type_code = cur.take_long()?;
let row_count = cur.take_long()?;
let column_count = cur.take_long()?;
let rows_to_read = if type_code == 211 {
1
} else {
usize::try_from(row_count).ok()?
};
let columns_to_read = if type_code == 211 {
0
} else {
usize::try_from(column_count).ok()?
};
let pairs_per_row = 1usize
.checked_add(columns_to_read)?
.checked_add(usize::from(revision && type_code != 211))?;
let tokens_per_row = pairs_per_row.checked_mul(2)?;
let rows_to_read = bounded_len(
cadmpeg_core::decode::u64_from_index(rows_to_read),
tokens_per_row,
cur.rest().len(),
)?;
let mut rows = Vec::new();
for _ in 0..rows_to_read {
let parameters = [cur.take_f64()?, cur.take_f64()?];
let mut columns = Vec::new();
if type_code != 211 {
columns = Vec::new();
for _ in 0..columns_to_read {
columns.push([cur.take_f64()?, cur.take_f64()?]);
}
}
let extra = if revision && type_code != 211 {
Some([cur.take_f64()?, cur.take_f64()?])
} else {
None
};
rows.push(LoftSubdataRow {
parameters,
columns,
extra,
});
}
if type_code == 211 {
let [row] = rows.as_slice() else {
return None;
};
Some(LoftSubdata::type_211(
[row_count, column_count],
row.parameters,
))
} else {
LoftSubdata::table(type_code, rows)
}
}
pub(super) fn loft_profile_data(
cur: &mut Cur<'_>,
type_code: i64,
) -> Option<ClassicLoftProfileData> {
let EmbeddedSupport { surface, chart, .. } = embedded_support(cur)?;
let saved = cur.pos();
let pcurve = if cur.take_ident() == Some("nullbs") {
None
} else {
cur.set_pos(saved);
let (mut pcurve, end) = pcurve_block_with_end(cur.toks(), cur.pos())?;
cur.set_pos(end);
chart.normalize(&mut pcurve)?;
Some(pcurve)
};
let first_flag = cur.take_bool()?;
let asm_extension = cur.take_long()?;
let subdata = loft_subdata(cur)?;
let direction = if cur.take_bool()? {
let value = cur.take_vector3()?;
Some(Vector3::new(value[0], value[1], value[2]))
} else {
None
};
Some(ClassicLoftProfileData {
type_code,
surface,
pcurve,
first_flag,
asm_extension,
subdata,
direction,
})
}
fn loft_section(cur: &mut Cur<'_>) -> Option<Vec<EmbeddedLoftSectionEntry>> {
let count = usize::try_from(cur.take_long()?).ok()?;
let count = bounded_len(
cadmpeg_core::decode::u64_from_index(count),
1,
cur.rest().len(),
)?;
let mut entries = Vec::new();
for _ in 0..count {
let parameter = cur.take_f64()?;
let member_count = usize::try_from(cur.take_long()?).ok()?;
let member_count = bounded_len(
cadmpeg_core::decode::u64_from_index(member_count),
1,
cur.rest().len(),
)?;
let mut profile = Vec::new();
for _ in 0..member_count {
let type_code = cur.take_long()?;
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
let data = loft_profile_data(cur, type_code)?;
profile.push(EmbeddedLoftProfileMember {
curve,
data: LoftProfileData::Classic(data),
});
}
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
let auxiliary_count = usize::try_from(cur.take_long()?).ok()?;
let auxiliary_count = bounded_len(
cadmpeg_core::decode::u64_from_index(auxiliary_count),
1,
cur.rest().len(),
)?;
let mut auxiliaries = Vec::new();
for _ in 0..auxiliary_count {
let (auxiliary, auxiliary_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(auxiliary_end);
auxiliaries.push(auxiliary);
}
let flag = cur.take_long()?;
entries.push(EmbeddedLoftSectionEntry {
parameter,
profile,
path: EmbeddedLoftPath {
layout: EmbeddedLoftPathLayout::Legacy(curve),
auxiliaries,
flag,
},
});
}
Some(entries)
}
fn revision_loft(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
span: &[Token],
position: usize,
resolver: Option<&SubtypeTable>,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let table = resolver?;
let mut cur = Cur::at(span, position);
let revision = PositiveI64::new(cur.take_long()?)?;
let asm_extension_present = revision_loft_carries_asm_extension(table);
let sections = [
match revision_loft_section(ctx, &mut cur, table, asm_extension_present)? {
Ok(section) => section,
Err(error) => return Some(Err(error)),
},
match revision_loft_section(ctx, &mut cur, table, asm_extension_present)? {
Ok(section) => section,
Err(error) => return Some(Err(error)),
},
];
let wrap_ranges = [
[
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
],
[
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
],
];
let mut flags = [false; 4];
for flag in &mut flags {
*flag = cur.take_bool()?;
}
let ints = [cur.take_long()?, cur.take_long()?];
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
Some(Ok(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::Loft(EmbeddedLoft {
sections,
layout: EmbeddedLoftLayout::Revision(
Box::new(cadmpeg_ir::geometry::LoftRevisionForm {
revision,
flags,
ints,
cache: cache.into_form()?,
discontinuities,
tail_flag,
}),
wrap_ranges,
),
}),
)))
}
fn loft_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
use cadmpeg_ir::geometry::LoftBridgeToken;
let names = ["loft_spl_sur", "loftsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) && name == "loft_spl_sur" {
if let Some(decoded) = revision_loft(ctx, span, cur.pos(), resolver) {
return Some(decoded);
}
}
let sections = [loft_section(&mut cur)?, loft_section(&mut cur)?];
let parameter_ranges = [
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
];
let closures = [cur.take_enum()?, cur.take_enum()?];
let singularities = [cur.take_enum()?, cur.take_enum()?];
let mode = cur.take_long()?;
let mut bridge = Vec::new();
while toks::marker_at(span, cur.pos()).is_none() {
match cur.peek()? {
Token::True | Token::False => {
bridge.push(LoftBridgeToken::Boolean(cur.take_bool()?));
}
Token::Long(_) => bridge.push(LoftBridgeToken::Integer(cur.take_long()?)),
Token::Double(_) => bridge.push(LoftBridgeToken::Double(cur.take_f64()?)),
Token::Enum(_) => bridge.push(LoftBridgeToken::Enum(cur.take_enum()?)),
Token::Str(_) => {
let value = cur.take_str()?;
let value = value.to_owned();
bridge.push(LoftBridgeToken::Text(value));
}
_ => return None,
}
}
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = optional_trailing_cache_tolerance(&mut cur)?.value();
Some(Ok(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Loft(EmbeddedLoft {
sections,
layout: EmbeddedLoftLayout::Legacy {
ranges: parameter_ranges,
closures,
singularities,
mode,
bridge,
},
}),
cache_fit_tolerance,
)))
}
fn revision_cl_scale(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
table: &SubtypeTable,
asm_extension_present: bool,
) -> Option<Result<(Vec<EmbeddedLoftProfileMember>, EmbeddedLoftPath), cadmpeg_core::CodecError>> {
let member_count = usize::try_from(cur.take_long()?).ok()?;
let member_count = bounded_len(
cadmpeg_core::decode::u64_from_index(member_count),
1,
cur.rest().len(),
)?;
let mut profile = Vec::new();
for _ in 0..member_count {
let type_code = cur.take_long()?;
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let data = match revision_loft_profile_data(
ctx,
cur,
table,
type_code,
asm_extension_present,
endpoints,
)? {
Ok(data) => data,
Err(error) => return Some(Err(error)),
};
profile.push(EmbeddedLoftProfileMember { curve, data });
}
let saved = cur.pos();
let path_curve = if cur.take_ident() == Some("null_curve") {
None
} else {
cur.set_pos(saved);
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
Some(EmbeddedRevisionLoftPathCurve {
geometry: curve,
endpoints,
})
};
let auxiliary_count = usize::try_from(cur.take_long()?).ok()?;
let auxiliary_count = bounded_len(
cadmpeg_core::decode::u64_from_index(auxiliary_count),
1,
cur.rest().len(),
)?;
let mut auxiliaries = Vec::new();
for _ in 0..auxiliary_count {
let (auxiliary, auxiliary_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(auxiliary_end);
auxiliaries.push(auxiliary);
}
let flag = cur.take_long()?;
Some(Ok((
profile,
EmbeddedLoftPath {
layout: EmbeddedLoftPathLayout::Revision(path_curve),
auxiliaries,
flag,
},
)))
}
fn revision_compound_loft(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
span: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let table = resolver?;
let mut cur = Cur::at(span, 2);
let revision = PositiveI64::new(cur.take_long()?)?;
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let asm_extension_present = revision_loft_carries_asm_extension(table);
let (base_profile, base_path) =
match revision_cl_scale(ctx, &mut cur, table, asm_extension_present)? {
Ok(scale) => scale,
Err(error) => return Some(Err(error)),
};
let entry_count = usize::try_from(cur.take_long()?).ok()?;
let entry_count = bounded_len(
cadmpeg_core::decode::u64_from_index(entry_count),
1,
cur.rest().len(),
)?;
let mut entries = Vec::new();
for _ in 0..entry_count {
let (profile, path) = match revision_cl_scale(ctx, &mut cur, table, asm_extension_present)?
{
Ok(scale) => scale,
Err(error) => return Some(Err(error)),
};
let parameter = cur.take_f64()?;
entries.push(EmbeddedLoftSectionEntry {
parameter,
profile,
path,
});
}
let flags = [cur.take_bool()?, cur.take_bool()?];
let kind = cur.take_long()?;
(kind == 0).then_some(())?;
let kind_flags = [cur.take_bool()?, cur.take_bool()?];
let selector = cur.take_long()?;
let direction = if selector == 0 {
let value = cur.take_vector3()?;
EmbeddedCompoundLoftDirection::Vector(Vector3::new(value[0], value[1], value[2]))
} else {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
EmbeddedCompoundLoftDirection::Curve {
selector: NonZeroI64::new(selector)?,
curve,
}
};
let interval = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let tail = match interval {
[None, None] => cadmpeg_ir::geometry::RevisionCompoundLoftTail::Unbounded {},
[Some(value), None] => {
cadmpeg_ir::geometry::RevisionCompoundLoftTail::LowerBound { lower: value }
}
[None, Some(value)] => {
cadmpeg_ir::geometry::RevisionCompoundLoftTail::UpperBound { upper: value }
}
[Some(lower), Some(upper)] => {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
cadmpeg_ir::geometry::RevisionCompoundLoftTail::Curve {
interval: [lower, upper],
curve,
}
}
};
cur.at_scope_end().then_some(())?;
Some(Ok(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::RevisionCompoundLoft(Box::new(
EmbeddedRevisionCompoundLoft {
revision,
cache: cache.into_form()?,
discontinuities,
tail_flag,
base_profile,
base_path,
entries,
flags,
kind_flags,
direction,
tail,
},
)),
)))
}
fn compound_loft_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let (start, _) = toks::find_owned_subtype_marker(toks, &["cl_loft_spl_sur"])?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
return revision_compound_loft(ctx, span, resolver);
}
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let scales = Box::new([
compound_loft_scale(&mut cur)?.value(),
compound_loft_scale(&mut cur)?.value(),
compound_loft_scale(&mut cur)?.value(),
compound_loft_scale(&mut cur)?.value(),
]);
let fifth_scale = if matches!(cur.peek(), Some(Token::Long(_))) {
compound_loft_scale(&mut cur)?.value().map(Box::new)
} else {
None
};
let flags = [cur.take_bool()?, cur.take_bool()?];
let kind = cur.take_long()?;
let tail = match kind {
6 => {
let tail_flags = [cur.take_bool()?, cur.take_bool()?];
let scale = Box::new(compound_loft_scale(&mut cur)?.value()?);
let selector = cur.take_long()?;
let direction = cur.take_vector3()?;
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let (curve, _) = curve_block(span, cur.pos())?;
EmbeddedCompoundLoftTail::Six {
flags: tail_flags,
scale,
selector,
direction: Vector3::new(direction[0], direction[1], direction[2]),
parameter_range,
curve,
}
}
7 => {
let first_flag = cur.take_bool()?;
let first_scale = compound_loft_scale(&mut cur)?.value().map(Box::new);
let second_flag = cur.take_bool()?;
let second_scale = Box::new(compound_loft_scale(&mut cur)?.value()?);
let selector = cur.take_long()?;
let direction = cur.take_vector3()?;
let trailing_flags = [cur.take_bool()?, cur.take_bool()?];
EmbeddedCompoundLoftTail::Seven {
first_flag,
first_scale,
second_flag,
second_scale,
selector,
direction: Vector3::new(direction[0], direction[1], direction[2]),
trailing_flags,
}
}
0 => {
let tail_flags = [cur.take_bool()?, cur.take_bool()?];
let selector = cur.take_long()?;
let direction = if selector == 0 {
let value = cur.take_vector3()?;
EmbeddedCompoundLoftDirection::Vector(Vector3::new(value[0], value[1], value[2]))
} else {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
EmbeddedCompoundLoftDirection::Curve {
selector: NonZeroI64::new(selector)?,
curve,
}
};
let trailing_flags = [cur.take_bool()?, cur.take_bool()?];
EmbeddedCompoundLoftTail::Zero {
flags: tail_flags,
direction,
trailing_flags,
}
}
_ => return None,
};
Some(Ok(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::CompoundLoft(Box::new(EmbeddedCompoundLoft {
scales,
fifth_scale,
flags,
tail,
})),
cache_fit_tolerance,
)))
}
fn scaled_compound_loft_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["scaled_cloft_spl_sur", "sclclftsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let singularity = cur.take_enum()?;
let (shape, cache_fit_tolerance) = if cur.peek().is_some_and(Token::is_payload_ident) {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let tolerance = cur.take_f64()? * LEN_TO_MM;
(EmbeddedScaledCompoundLoftShape::Full, Some(tolerance))
} else {
let parameter_ranges = [
[cur.take_range_value()?, cur.take_range_value()?],
[cur.take_range_value()?, cur.take_range_value()?],
];
let parameters = [cur.take_float_array()?, cur.take_float_array()?];
(
EmbeddedScaledCompoundLoftShape::None {
parameter_ranges,
parameters,
},
None,
)
};
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
let scales = Box::new([
compound_loft_scale(&mut cur)?.value(),
compound_loft_scale(&mut cur)?.value(),
compound_loft_scale(&mut cur)?.value(),
]);
let flags = [cur.take_bool()?, cur.take_bool()?];
let selector = cur.take_long()?;
let extended = cur.take_bool()?;
let branch = if extended {
let first_scale = compound_loft_scale(&mut cur)?.value().map(Box::new);
if cur.take_bool()? {
let second_scale = Box::new(compound_loft_scale(&mut cur)?.value()?);
let selector = cur.take_long()?;
let direction = cur.take_vector3()?;
EmbeddedScaledCompoundLoftBranch::ExtendedVector {
first_scale,
second_scale,
selector,
direction: Vector3::new(direction[0], direction[1], direction[2]),
}
} else {
let flag = cur.take_bool()?;
let singularity = cur.take_enum()?;
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
EmbeddedScaledCompoundLoftBranch::ExtendedCurve {
scale: first_scale,
flag,
singularity,
curve,
}
}
} else {
let flag = cur.take_bool()?;
let selector = cur.take_long()?;
let direction = if selector == 0 {
let direction = cur.take_vector3()?;
EmbeddedCompoundLoftDirection::Vector(Vector3::new(
direction[0],
direction[1],
direction[2],
))
} else {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
EmbeddedCompoundLoftDirection::Curve {
selector: NonZeroI64::new(selector)?,
curve,
}
};
EmbeddedScaledCompoundLoftBranch::Direct { flag, direction }
};
let trailing_flags = [cur.take_bool()?, cur.take_bool()?];
let tail_kind = cur.take_long()?;
let first = cur.take_vector3()?;
let second = cur.take_vector3()?;
let tail_singularity = cur.take_enum()?;
let (tail_curve, _) = curve_block(span, cur.pos())?;
Some(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::ScaledCompoundLoft(Box::new(
EmbeddedScaledCompoundLoft {
singularity,
shape,
discontinuities,
discontinuity_flag,
scales,
flags,
selector,
branch,
trailing_flags,
tail_kind,
tail_directions: [
Vector3::new(first[0], first[1], first[2]),
Vector3::new(second[0], second[1], second[2]),
],
tail_singularity,
tail_curve,
},
)),
cache_fit_tolerance,
))
}
pub(super) fn law_expression(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
depth: usize,
) -> Option<Result<EmbeddedLawExpression, cadmpeg_core::CodecError>> {
law_expression_resolving(ctx, cur, depth, None)
}
fn sweep_law_expression(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
) -> Option<Result<EmbeddedLawExpression, cadmpeg_core::CodecError>> {
if matches!(cur.peek(), Some(Token::Str(_))) {
let source = cur.take_str()?;
source
.chars()
.any(|character| !character.is_whitespace())
.then_some(())?;
let copied = source.to_owned();
return Some(Ok(EmbeddedLawExpression::Text(
cadmpeg_core::text::NonBlankString::new(copied)?,
)));
}
law_expression(ctx, cur, 0)
}
fn law_expression_resolving(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
depth: usize,
resolver: Option<&SubtypeTable>,
) -> Option<Result<EmbeddedLawExpression, cadmpeg_core::CodecError>> {
let _depth_guard = propagate_resource!(ctx.enter_nested("ASM law expression"));
if depth > 64 {
return None;
}
match cur.peek()? {
Token::Long(_) => {
return Some(Ok(EmbeddedLawExpression::Integer(cur.take_long()?)));
}
Token::Double(_) => return Some(Ok(EmbeddedLawExpression::Double(cur.take_f64()?))),
Token::Position(_) => {
let value = cur.take_position()?;
return Some(Ok(EmbeddedLawExpression::Point(Point3::new(
value[0] * LEN_TO_MM,
value[1] * LEN_TO_MM,
value[2] * LEN_TO_MM,
))));
}
Token::Vector3(_) => {
let value = cur.take_vector3()?;
return Some(Ok(EmbeddedLawExpression::Vector(Vector3::new(
value[0], value[1], value[2],
))));
}
_ => {}
}
let operator = cur.take_str()?;
match operator {
"null_law" => Some(Ok(EmbeddedLawExpression::Null)),
"TRANS" => {
if matches!(cur.peek(), Some(Token::Vector3(_))) {
let mut vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let scale = cur.take_f64()?;
let flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
return Some(Ok(EmbeddedLawExpression::TransformVec {
vectors,
scale,
flags,
}));
}
let mut scalars = [0.0; 13];
for scalar in &mut scalars {
*scalar = cur.take_f64()?;
}
let enums = [cur.take_enum()?, cur.take_enum()?, cur.take_enum()?];
Some(Ok(EmbeddedLawExpression::Transform { scalars, enums }))
}
"EDGE" => {
let (curve, endpoints) = if let Some(decoded) = curve_block(cur.toks(), cur.pos()) {
let (curve, end) = decoded;
cur.set_pos(end);
let endpoints = matches!(cur.peek(), Some(Token::True | Token::False))
.then(|| {
Some([
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
])
})
.flatten();
(curve, endpoints)
} else {
let table = resolver?;
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = Some([
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
]);
(curve, endpoints)
};
let parameters = [cur.take_f64()?, cur.take_f64()?];
Some(Ok(EmbeddedLawExpression::Edge {
curve,
endpoints,
parameters,
}))
}
"SPLINE_LAW" => {
let native_id = cur.take_long()?;
let knots = cur.take_float_array()?;
let controls = cur.take_float_array()?;
let point = cur.take_position()?;
Some(Ok(EmbeddedLawExpression::Spline {
native_id,
knots,
controls,
point: Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
),
}))
}
_ => {
let arity = match operator {
"COS" | "SIN" | "TAN" | "COT" | "SEC" | "CSC" | "COSH" | "SINH" | "TANH"
| "COTH" | "SECH" | "CSCH" | "ARCCOS" | "ARCSIN" | "ARCTAN" | "ARCOT"
| "ARCSEC" | "ARCCSC" | "ARCCOSH" | "ARCSINH" | "ARCTANH" | "ARCOTH"
| "ARCSECH" | "ARCCSCH" | "ABS" | "EXP" | "LN" | "LOG" | "SIGN" | "SIZE"
| "SET" | "SQRT" | "NORM" | "NOT" => 1,
"CROSS" | "DOT" | "DCUR" | "O" | "ROTATE" | "TERM" => 2,
"VEC" | "DSURF" => 3,
_ => return None,
};
let mut operands = Vec::new();
for _ in 0..arity {
operands.push(propagate_resource!(law_expression_resolving(
ctx,
cur,
depth + 1,
resolver
)?));
}
let operator = operator.to_owned();
Some(Ok(EmbeddedLawExpression::Algebraic { operator, operands }))
}
}
}
pub(super) fn law_formula(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
) -> Option<Result<EmbeddedLawFormula, cadmpeg_core::CodecError>> {
law_formula_resolving(ctx, cur, None)
}
fn law_formula_resolving(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
resolver: Option<&SubtypeTable>,
) -> Option<Result<EmbeddedLawFormula, cadmpeg_core::CodecError>> {
let name = cur.take_str()?.to_owned();
if name == "null_law" {
return Some(Ok(EmbeddedLawFormula::Null));
}
let count = usize::try_from(cur.take_long()?).ok()?;
if count > 100_000 {
return None;
}
let mut variables = Vec::new();
for _ in 0..count {
variables.push(propagate_resource!(law_expression_resolving(
ctx, cur, 0, resolver
)?));
}
Some(Ok(EmbeddedLawFormula::Named {
name: cadmpeg_core::text::NonBlankString::new(name)?,
variables,
}))
}
fn skin_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let names = ["skin_spl_sur", "skinsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let surface_boolean = cur.take_enum()?;
let surface_normal = cur.take_enum()?;
let surface_direction = cur.take_enum()?;
let count = cur.take_long()?;
let parameter = cur.take_f64()?;
let inner_count = cur.take_long()?;
let layout = if cur.peek().is_some_and(Token::is_payload_ident) {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
let subdata = loft_subdata(&mut cur)?;
let first_tail = cur.take_long()?;
let (secondary_curve, secondary_end) = curve_block(span, cur.pos())?;
cur.set_pos(secondary_end);
let second_tail = cur.take_long()?;
EmbeddedSkinSurfaceLayout::Compact {
inner_count,
curve,
subdata,
first_tail,
secondary_curve,
second_tail,
}
} else {
let profile_count = usize::try_from(inner_count).ok()?;
if profile_count > 100_000 {
return None;
}
let mut profiles = Vec::new();
for _ in 0..profile_count {
let type_code = cur.take_long()?;
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
let data = loft_profile_data(&mut cur, type_code)?;
profiles.push(EmbeddedLoftProfileMember { curve, data });
}
let (path, path_end) = curve_block(span, cur.pos())?;
cur.set_pos(path_end);
let tail = [cur.take_long()?, cur.take_long()?];
EmbeddedSkinSurfaceLayout::Profiles {
profiles,
path,
tail,
}
};
let direction = cur.take_vector3()?;
let trailing_parameter = cur.take_f64()?;
let formula = propagate_resource!(law_formula(ctx, &mut cur)?);
let (parameter_curve, parameter_curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(parameter_curve_end);
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
Some(Ok(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Skin(Box::new(EmbeddedSkinSurface {
surface_boolean,
surface_normal,
surface_direction,
count,
parameter,
layout,
direction: Vector3::new(direction[0], direction[1], direction[2]),
trailing_parameter,
formula,
parameter_curve,
discontinuities,
discontinuity_flag,
})),
cache_fit_tolerance,
)))
}
pub(super) fn law_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let names = ["law_spl_sur", "lawsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let parameter_ranges = if matches!(cur.peek(), Some(Token::Double(_))) {
Some([
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
])
} else {
None
};
let primary = propagate_resource!(law_formula(ctx, &mut cur)?);
let count = usize::try_from(cur.take_long()?).ok()?;
if count > 100_000 {
return None;
}
let mut additional = Vec::new();
for _ in 0..count {
additional.push(propagate_resource!(law_formula(ctx, &mut cur)?));
}
let selector = if parameter_ranges.is_some()
&& toks::marker_at(span, cur.pos()) == Some(crate::nurbs::reader::BsplineMarker::Nubs)
{
0
} else {
cur.take_enum()?
};
let (tail, cache_fit_tolerance) = match selector {
0 => {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let fit_tolerance = cur.take_f64()? * LEN_TO_MM;
(
cadmpeg_ir::geometry::LawSurfaceTail::Full {
cache: cadmpeg_ir::geometry::LegacyCache::try_new(fit_tolerance).ok()?,
},
Some(fit_tolerance),
)
}
1 => {
let parameters = [cur.take_float_array()?, cur.take_float_array()?];
let fit_tolerance =
cadmpeg_ir::geometry::FitTolerance::try_new(cur.take_f64()? * LEN_TO_MM).ok()?;
let closures = [cur.take_enum()?, cur.take_enum()?];
let singularities = [cur.take_enum()?, cur.take_enum()?];
(
cadmpeg_ir::geometry::LawSurfaceTail::Summary {
parameters,
fit_tolerance,
closures,
singularities,
},
None,
)
}
2 => {
let parameter_ranges = [
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
];
let closures = [cur.take_enum()?, cur.take_enum()?];
let singularities = [cur.take_enum()?, cur.take_enum()?];
(
cadmpeg_ir::geometry::LawSurfaceTail::None {
parameter_ranges,
closures,
singularities,
},
None,
)
}
3 => (cadmpeg_ir::geometry::LawSurfaceTail::Historical {}, None),
4 => (cadmpeg_ir::geometry::LawSurfaceTail::Optimal {}, None),
_ => return None,
};
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
Some(Ok(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Law(Box::new(EmbeddedLawSurface {
parameter_ranges,
primary,
additional,
tail,
discontinuities,
})),
cache_fit_tolerance,
)))
}
pub(super) fn sub_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["sub_spl_sur", "subsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let parameter_ranges = [
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
];
let support = embedded_surface(&mut cur)?;
Some(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::SubSurface {
support,
parameter_ranges,
},
None,
))
}
fn net_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let names = ["net_spl_sur", "netsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let sections = Box::new([loft_section(&mut cur)?, loft_section(&mut cur)?]);
let mut frame_parameters = [0.0; 12];
for parameter in &mut frame_parameters {
*parameter = cur.take_f64()?;
}
let flag = cur.take_long()?;
let mut directions = [Vector3::new(0.0, 0.0, 0.0); 4];
for direction in &mut directions {
let value = cur.take_vector3()?;
*direction = Vector3::new(value[0], value[1], value[2]);
}
let formulas = [
propagate_resource!(law_formula(ctx, &mut cur)?),
propagate_resource!(law_formula(ctx, &mut cur)?),
propagate_resource!(law_formula(ctx, &mut cur)?),
propagate_resource!(law_formula(ctx, &mut cur)?),
];
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
Some(Ok(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Net(Box::new(EmbeddedNetSurface {
sections,
frame_parameters,
flag,
directions,
formulas: Box::new(formulas),
discontinuities,
discontinuity_flag,
})),
cache_fit_tolerance,
)))
}
fn sweep_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let names = ["sweep_spl_sur", "sweep_sur", "sweepsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "sweep_sur").then_some(())?;
return revision_sweep_sur(ctx, span, cur.pos(), resolver?);
}
let primary_kind = cur.take_enum()?;
let layout = if cur.peek().is_some_and(Token::is_payload_ident) {
let (profile, profile_end) = curve_block(span, cur.pos())?;
cur.set_pos(profile_end);
let (spine, spine_end) = curve_block(span, cur.pos())?;
cur.set_pos(spine_end);
let secondary_kind = cur.take_enum()?;
let mut directions = [Vector3::new(0.0, 0.0, 0.0); 5];
for direction in &mut directions {
let value = cur.take_vector3()?;
*direction = Vector3::new(value[0], value[1], value[2]);
}
let origin = cur.take_position()?;
let mut parameters = [0.0; 4];
for parameter in &mut parameters {
*parameter = cur.take_f64()?;
}
let formulas = [
propagate_resource!(law_formula(ctx, &mut cur)?),
propagate_resource!(law_formula(ctx, &mut cur)?),
propagate_resource!(law_formula(ctx, &mut cur)?),
];
LegacySweepLayout::ProfileFirst {
profile: Box::new(profile),
spine: Box::new(spine),
secondary_kind,
directions,
origin: Point3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
),
parameters,
formulas: Box::new(formulas),
}
} else {
let mode = cur.take_long()?;
let (profile, profile_end) = curve_block(span, cur.pos())?;
cur.set_pos(profile_end);
let profile_range = [cur.take_f64()?, cur.take_f64()?];
let profile_frame = if cur.take_bool()? {
let point = cur.take_position()?;
let vector = cur.take_vector3()?;
Some((
Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
),
Vector3::new(vector[0], vector[1], vector[2]),
))
} else {
None
};
let point = cur.take_position()?;
let origin = Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
);
let mut directions = [Vector3::new(0.0, 0.0, 0.0); 3];
for direction in &mut directions {
let value = cur.take_vector3()?;
*direction = Vector3::new(value[0], value[1], value[2]);
}
if matches!(cur.peek(), Some(Token::Long(_))) {
let branch = cur.take_long()?;
let trajectory_flag = cur.take_bool()?;
let (path, path_end) = curve_block(span, cur.pos())?;
cur.set_pos(path_end);
let path_range = [cur.take_f64()? * LEN_TO_MM, cur.take_f64()? * LEN_TO_MM];
let path_parameter = cur.take_f64()?;
let profile = SweepProfile {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
path,
path_range,
path_parameter,
};
let tail = match branch {
1 => {
let formula_flag = cur.take_bool()?;
let formula = propagate_resource!(law_formula(ctx, &mut cur)?);
let trailing_flag = cur.take_bool()?;
SweepTail::LawOrFormula(SweepLawOrFormula::Formula {
trajectory_flag,
formula_flag,
formula,
trailing_flag,
})
}
2 => {
let guide_flags = [cur.take_bool()?, cur.take_bool()?];
let (guide_curve, guide_end) = curve_block(span, cur.pos())?;
cur.set_pos(guide_end);
let guide_range = [cur.take_f64()?, cur.take_f64()?];
let guide_modes = [cur.take_long()?, cur.take_long()?];
let mut guide_parameters = [0.0; 6];
for parameter in &mut guide_parameters {
*parameter = cur.take_f64()?;
}
let trailing_flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
SweepTail::Guide {
trajectory_flag,
guide_flags,
guide_curve,
guide_range,
guide_modes,
guide_parameters,
trailing_flags,
}
}
3 => {
let singularity = cur.take_enum()?;
let support_surface = embedded_surface(&mut cur)?;
let auxiliary_curve = if cur.take_bool()? {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
Some(curve)
} else {
None
};
let support_flag = cur.take_bool()?;
let legacy_flag = matches!(cur.peek(), Some(Token::True | Token::False))
.then(|| cur.take_bool())
.flatten();
SweepTail::Surface {
trajectory_flag,
singularity,
support_surface,
auxiliary_curve,
support_flag,
legacy_flag,
}
}
_ => return None,
};
LegacySweepLayout::Sweep {
profile: Box::new(profile),
tail,
}
} else {
let first_law = propagate_resource!(sweep_law_expression(ctx, &mut cur)?);
let first_mode = cur.take_long()?;
let first_range = [cur.take_f64()?, cur.take_f64()?];
let vector = cur.take_vector3()?;
let law_direction = Vector3::new(vector[0], vector[1], vector[2]);
let path_mode = cur.take_long()?;
let path_flag = cur.take_bool()?;
let (path, path_end) = curve_block(span, cur.pos())?;
cur.set_pos(path_end);
let path_range = [cur.take_f64()?, cur.take_f64()?];
let path_parameter = cur.take_f64()?;
let second_law_flag = cur.take_bool()?;
let second_law = propagate_resource!(sweep_law_expression(ctx, &mut cur)?);
let formula_mode = cur.take_long()?;
let formula = propagate_resource!(law_formula(ctx, &mut cur)?);
let trailing_flag = cur.take_bool()?;
LegacySweepLayout::Sweep {
profile: Box::new(SweepProfile {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
path,
path_range,
path_parameter,
}),
tail: SweepTail::LawOrFormula(SweepLawOrFormula::Law {
first_law: Box::new(first_law),
first_mode,
first_range,
law_direction,
path_mode,
path_flag,
second_law_flag,
second_law: Box::new(second_law),
formula_mode,
formula,
trailing_flag,
}),
}
}
};
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
Some(Ok(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Sweep(Box::new(EmbeddedSweepSurface {
layout: EmbeddedSweepSurfaceLayout::Legacy {
primary_kind,
layout,
},
discontinuities,
discontinuity_flag,
})),
cache_fit_tolerance,
)))
}
fn revision_sweep_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
span: &[Token],
position: usize,
table: &SubtypeTable,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let mut cur = Cur::at(span, position);
let revision = PositiveI64::new(cur.take_long()?)?;
let primary_flag = cur.take_bool()?;
let mode = cur.take_long()?;
let profile = embedded_base_curve_resolving_refs(&mut cur, table)?;
let profile_endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let profile_range = [
cur.take_optional_range_value()?.value()?,
cur.take_optional_range_value()?.value()?,
];
let profile_frame = if cur.take_bool()? {
let point = cur.take_position()?;
let vector = cur.take_vector3()?;
Some((
Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
),
Vector3::new(vector[0], vector[1], vector[2]),
))
} else {
None
};
let point = cur.take_position()?;
let origin = Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
);
let mut directions = [Vector3::new(0.0, 0.0, 0.0); 3];
for direction in &mut directions {
let value = cur.take_vector3()?;
*direction = Vector3::new(value[0], value[1], value[2]);
}
let (profile, tail, path_endpoints) = if matches!(cur.peek(), Some(Token::Str(_))) {
let first_law = propagate_resource!(sweep_law_expression(ctx, &mut cur)?);
let first_mode = cur.take_long()?;
let first_range = [
cur.take_optional_range_value()?.value()?,
cur.take_optional_range_value()?.value()?,
];
let law_direction = cur.take_vector3()?;
let path_mode = cur.take_long()?;
let path_flag = cur.take_bool()?;
let path = embedded_base_curve_resolving_refs(&mut cur, table)?;
let path_endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let path_range = [
cur.take_optional_range_value()?.value()? * LEN_TO_MM,
cur.take_optional_range_value()?.value()? * LEN_TO_MM,
];
let path_parameter = cur.take_f64()?;
let second_law_flag = cur.take_bool()?;
let second_law = propagate_resource!(sweep_law_expression(ctx, &mut cur)?);
let formula_mode = cur.take_long()?;
let formula = propagate_resource!(law_formula_resolving(ctx, &mut cur, Some(table))?);
let trailing_flag = cur.take_bool()?;
let law_direction = Vector3::new(law_direction[0], law_direction[1], law_direction[2]);
(
SweepProfile {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
path,
path_range,
path_parameter,
},
SweepLawOrFormula::Law {
first_law: Box::new(first_law),
first_mode,
first_range,
law_direction,
path_mode,
path_flag,
second_law_flag,
second_law: Box::new(second_law),
formula_mode,
formula,
trailing_flag,
},
path_endpoints,
)
} else {
(cur.take_long()? == 1).then_some(())?;
let trajectory_flag = cur.take_bool()?;
let path = embedded_base_curve_resolving_refs(&mut cur, table)?;
let path_endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let path_range = [
cur.take_optional_range_value()?.value()? * LEN_TO_MM,
cur.take_optional_range_value()?.value()? * LEN_TO_MM,
];
let path_parameter = cur.take_f64()?;
let formula_flag = cur.take_bool()?;
let formula = propagate_resource!(law_formula_resolving(ctx, &mut cur, Some(table))?);
let trailing_flag = cur.take_bool()?;
(
SweepProfile {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
path,
path_range,
path_parameter,
},
SweepLawOrFormula::Formula {
trajectory_flag,
formula_flag,
formula,
trailing_flag,
},
path_endpoints,
)
};
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag: discontinuity_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
Some(Ok(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::Sweep(Box::new(EmbeddedSweepSurface {
layout: EmbeddedSweepSurfaceLayout::Revision {
form: cadmpeg_ir::geometry::SweepRevisionForm {
revision,
primary_flag,
profile_endpoints,
path_endpoints,
cache: cache.into_form()?,
},
profile: Box::new(profile),
tail,
},
discontinuities,
discontinuity_flag,
})),
)))
}
fn taper_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
use cadmpeg_ir::geometry::TaperSurfaceKind;
let names: &[(&str, u8)] = &[
("taper_spl_sur", 0),
("ortho_spl_sur", 1),
("orthosur", 1),
("edge_tpr_spl_sur", 2),
("shadow_tpr_spl_sur", 3),
("shadowtapersur", 3),
("ruled_tpr_spl_sur", 4),
("ruledtapersur", 4),
("swept_tpr_spl_sur", 5),
("swepttapersur", 5),
];
let candidates: Vec<&str> = names.iter().map(|(name, _)| *name).collect();
let (start, name) = toks::find_owned_subtype_marker(toks, &candidates)?;
let kind = names
.iter()
.find_map(|(candidate, kind)| (*candidate == name).then_some(*kind))?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "ortho_spl_sur").then_some(())?;
let table = resolver?;
let revision = PositiveI64::new(cur.take_long()?)?;
let crate::nurbs::proc_curve::EmbeddedSurfaceWithBounds {
surface: support,
chart,
bounds: support_bounds,
} = match optional_embedded_surface_with_bounds(ctx, &mut cur, table)? {
Ok(support) => support,
Err(error) => return Some(Err(error)),
};
let support = support.into_surface()?;
let reference = embedded_base_curve_resolving_refs(&mut cur, table)?;
let reference_endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let mut pcurve = nullable_embedded_pcurve(&mut cur)?.value();
if let Some(pcurve) = &mut pcurve {
chart.normalize(pcurve)?;
}
let parameter = cur.take_f64()?;
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let sense = cur.take_bool()?;
cur.at_scope_end().then_some(())?;
return Some(Ok(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::Taper {
support,
reference,
pcurve,
parameter,
taper: TaperSurfaceKind::Orthogonal { sense },
revision_form: Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds,
reference_endpoints,
second_endpoints: [None; 2],
flags: Vec::new(),
cache: cache.into_form()?,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}),
},
)));
}
let EmbeddedSupport {
surface: support,
chart,
..
} = embedded_support(&mut cur)?;
let (reference, reference_end) = curve_block(span, cur.pos())?;
cur.set_pos(reference_end);
let saved = cur.pos();
let pcurve = if cur.take_ident() == Some("nullbs") {
None
} else {
cur.set_pos(saved);
let (mut pcurve, end) = pcurve_block_with_end(span, cur.pos())?;
cur.set_pos(end);
chart.normalize(&mut pcurve)?;
Some(pcurve)
};
let parameter = cur.take_f64()?;
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = if matches!(cur.peek(), Some(Token::Double(_))) {
Some(cur.take_f64()? * LEN_TO_MM)
} else {
None
};
let take_draft = |cur: &mut Cur<'_>| {
let draft = cur.take_vector3()?;
Some(Vector3::new(draft[0], draft[1], draft[2]))
};
let taper = match kind {
0 => TaperSurfaceKind::Standard {},
1 => TaperSurfaceKind::Orthogonal {
sense: cur.take_bool()?,
},
2 => TaperSurfaceKind::Edge {
draft: take_draft(&mut cur)?,
},
3 => TaperSurfaceKind::Shadow {
draft: take_draft(&mut cur)?,
sine: cur.take_f64()?,
cosine: cur.take_f64()?,
},
4 => TaperSurfaceKind::Ruled {
draft: take_draft(&mut cur)?,
sine: cur.take_f64()?,
cosine: cur.take_f64()?,
factor: cur.take_f64()?,
},
5 => TaperSurfaceKind::Swept {
draft: take_draft(&mut cur)?,
sine: cur.take_f64()?,
cosine: cur.take_f64()?,
},
_ => return None,
};
cur.at_scope_end().then_some(())?;
Some(Ok(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Taper {
support,
reference,
pcurve,
parameter,
taper,
revision_form: None,
},
cache_fit_tolerance,
)))
}
fn comp_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let (start, _) = toks::find_owned_subtype_marker(toks, &["comp_spl_sur"])?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = if matches!(cur.peek(), Some(Token::Double(_))) {
Some(cur.take_f64()? * LEN_TO_MM)
} else {
None
};
let parameters = cur.take_float_array()?;
let mut components = Vec::new();
for parameter in parameters {
components.push(cadmpeg_ir::geometry::CompoundComponent {
parameter,
component: embedded_surface(&mut cur)?,
});
}
cur.at_scope_end().then_some(())?;
Some(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Compound { components },
cache_fit_tolerance,
))
}
pub enum RevisionSurfaceCache {
Solved {
fit_tolerance: f64,
domains: [[f64; 2]; 2],
},
Parameterized(RevisionSurfaceParameterization),
}
impl RevisionSurfaceCache {
pub(super) fn into_form(self) -> Option<RevisionCacheForm> {
Some(match self {
Self::Solved { fit_tolerance, .. } => RevisionCacheForm::SolvedCache {
fit_tolerance: cadmpeg_ir::geometry::FitTolerance::try_new(fit_tolerance).ok()?,
},
Self::Parameterized(parameters) => RevisionCacheForm::Parameterization(parameters),
})
}
}
pub struct RevisionSurfaceTail {
pub cache: RevisionSurfaceCache,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
}
pub fn revision_surface_tail(cur: &mut Cur<'_>) -> Option<RevisionSurfaceTail> {
let enumeration = cur.take_enum()?;
let cache = match enumeration {
0 => {
let (cache, cache_end) = surface_block(cur.toks(), cur.pos())?;
cur.set_pos(cache_end);
let domains = [
[*cache.u_knots().first()?, *cache.u_knots().last()?],
[*cache.v_knots().first()?, *cache.v_knots().last()?],
];
RevisionSurfaceCache::Solved {
fit_tolerance: cur.take_f64()? * LEN_TO_MM,
domains,
}
}
2 => {
let u_interval = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let v_interval = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
RevisionSurfaceCache::Parameterized(RevisionSurfaceParameterization {
u_interval,
v_interval,
u_closure: cur.take_enum()?,
v_closure: cur.take_enum()?,
u_singularity: cur.take_enum()?,
v_singularity: cur.take_enum()?,
})
}
_ => return None,
};
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let tail_flag = cur.take_bool()?;
Some(RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
})
}
fn off_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let names = ["off_spl_sur", "offsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let modern = name == "off_spl_sur";
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
modern.then_some(())?;
let table = resolver?;
let revision = PositiveI64::new(cur.take_long()?)?;
let crate::nurbs::proc_curve::EmbeddedSurfaceWithBounds {
surface: support,
bounds: support_bounds,
..
} = match optional_embedded_surface_with_bounds(ctx, &mut cur, table)? {
Ok(support) => support,
Err(error) => return Some(Err(error)),
};
let support = support.into_surface()?;
let distance = cur.take_f64()? * LEN_TO_MM;
let mut flags = Vec::new();
for _ in 0..4 {
flags.push(cur.take_bool()?);
}
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
return Some(Ok(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::Offset {
support,
distance,
layout: EmbeddedOffsetLayout::Revision(Box::new(
cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds,
reference_endpoints: [None; 2],
second_endpoints: [None; 2],
flags: flags.try_into().ok()?,
cache: cache.into_form()?,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
},
)),
},
)));
}
let support = embedded_surface(&mut cur)?;
let distance = cur.take_f64()? * LEN_TO_MM;
let u_sense = cur.take_enum()?;
let v_sense = cur.take_enum()?;
let extension_flags = if modern {
let first = cur.take_bool()?;
if first {
cadmpeg_ir::geometry::LegacyExtensionFlags::Enabled {
secondary: cur.take_bool()?,
tertiary: matches!(cur.peek(), Some(Token::True | Token::False))
.then(|| cur.take_bool())
.flatten(),
}
} else {
cadmpeg_ir::geometry::LegacyExtensionFlags::Disabled {}
}
} else {
cadmpeg_ir::geometry::LegacyExtensionFlags::Absent {}
};
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = optional_trailing_cache_tolerance(&mut cur)?.value();
Some(Ok(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Offset {
support,
distance,
layout: EmbeddedOffsetLayout::Legacy {
u_sense,
v_sense,
extension: extension_flags,
},
},
cache_fit_tolerance,
)))
}
fn rot_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let names = ["rot_spl_sur", "rotsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "rot_spl_sur").then_some(())?;
let revision = PositiveI64::new(cur.take_long()?)?;
let table = resolver?;
let profile = embedded_base_curve_resolving_refs(&mut cur, table)?;
let profile_endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let origin = cur.take_position()?;
let axis = cur.take_vector3()?;
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
let angular_interval = match &cache {
RevisionSurfaceCache::Solved { domains, .. } => domains[1],
RevisionSurfaceCache::Parameterized(_) => return None,
};
let parameter_interval = [
profile_endpoints[0].unwrap_or(*profile.knots().first()?),
profile_endpoints[1].unwrap_or(*profile.knots().last()?),
];
return Some(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::Revolution {
directrix: CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(profile)),
axis_origin: Point3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
),
axis_direction: UnitVector3::normalized_nonzero(
cadmpeg_ir::features::FiniteVector3::new(Vector3::from(axis))?,
)?,
angular_interval,
parameter_interval,
revision_form: Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds: [None; 4],
reference_endpoints: profile_endpoints,
second_endpoints: [None; 2],
flags: Vec::new(),
cache: cache.into_form()?,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}),
},
));
}
let (directrix, directrix_end) = curve_block(span, cur.pos())?;
cur.set_pos(directrix_end);
let parameter_interval = [*directrix.knots().first()?, *directrix.knots().last()?];
let origin = cur.take_position()?;
let axis_origin = Point3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
);
let axis = cur.take_vector3()?;
let axis_direction = UnitVector3::normalized_nonzero(
cadmpeg_ir::features::FiniteVector3::new(Vector3::from(axis))?,
)?;
let (cache, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let angular_interval = [*cache.v_knots().first()?, *cache.v_knots().last()?];
let cache_fit_tolerance = optional_trailing_cache_tolerance(&mut cur)?.value();
Some(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Revolution {
directrix: CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(directrix)),
axis_origin,
axis_direction,
angular_interval,
parameter_interval,
revision_form: None,
},
cache_fit_tolerance,
))
}
fn sum_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let names = ["sum_spl_sur", "sumsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "sum_spl_sur").then_some(())?;
let revision = PositiveI64::new(cur.take_long()?)?;
let table = resolver?;
let first = embedded_base_curve_resolving_refs(&mut cur, table)?;
let first_endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let second = embedded_base_curve_resolving_refs(&mut cur, table)?;
let second_endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let origin = cur.take_position()?;
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
return Some(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::Sum {
first: CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(first)),
second: CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(second)),
basepoint: Vector3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
),
revision_form: Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds: [None; 4],
reference_endpoints: first_endpoints,
second_endpoints,
flags: Vec::new(),
cache: cache.into_form()?,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}),
},
));
}
let (first, first_end) = curve_block(span, cur.pos())?;
cur.set_pos(first_end);
let (second, second_end) = curve_block(span, cur.pos())?;
cur.set_pos(second_end);
let origin = cur.take_position()?;
let basepoint = Vector3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
);
let cache_fit_tolerance = if cur.at_scope_end() {
None
} else {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
optional_trailing_cache_tolerance(&mut cur)?.value()
};
Some(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Sum {
first: CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(first)),
second: CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(second)),
basepoint,
revision_form: None,
},
cache_fit_tolerance,
))
}
fn ruled_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["rule_sur", "rulesur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let (first, first_end) = curve_block(span, cur.pos())?;
cur.set_pos(first_end);
let (second, second_end) = curve_block(span, cur.pos())?;
cur.set_pos(second_end);
let cache_fit_tolerance = if cur.at_scope_end() {
None
} else {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
optional_trailing_cache_tolerance(&mut cur)?.value()
};
Some(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Ruled { first, second },
cache_fit_tolerance,
))
}
fn exact_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["exact_spl_sur", "exactsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "exact_spl_sur").then_some(())?;
let revision = PositiveI64::new(cur.take_long()?)?;
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let unextended_ranges = [
[
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
],
[
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
],
];
let extension = cur.take_enum()?;
cur.at_scope_end().then_some(())?;
return Some(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::Exact {
spline: cadmpeg_ir::geometry::ExactSpline::Revision {
intervals: unextended_ranges,
extension,
form: Box::new(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds: [None; 4],
reference_endpoints: [None; 2],
second_endpoints: [None; 2],
flags: Vec::new(),
cache: cache.into_form()?,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}),
},
},
));
}
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let parameter_ranges = [
[cur.take_range_value()?, cur.take_range_value()?],
[cur.take_range_value()?, cur.take_range_value()?],
];
let extension = cur.take_long()?;
cur.at_scope_end().then_some(())?;
Some(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Exact {
spline: cadmpeg_ir::geometry::ExactSpline::Legacy {
ranges: parameter_ranges,
extension,
cache: None,
},
},
cache_fit_tolerance,
))
}
fn t_spl_sur(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
enum Layout {
Legacy {
cache_fit_tolerance: f64,
discontinuities: [Vec<f64>; 6],
discontinuity_flag: bool,
parameter_ranges: [[f64; 2]; 2],
},
Revision(Box<cadmpeg_ir::geometry::RevisionSurfaceForm>),
}
let (start, _) = toks::find_owned_subtype_marker(toks, &["t_spl_sur"])?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let (layout, type_code);
if matches!(cur.peek(), Some(Token::Long(_))) {
let revision = PositiveI64::new(cur.take_long()?)?;
let RevisionSurfaceTail {
cache,
discontinuities: tail_discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let mut bounds = [None; 4];
for bound in &mut bounds {
*bound = cur.take_optional_range_value()?.value();
}
type_code = cur.take_enum()?;
layout = Layout::Revision(Box::new(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds: bounds,
reference_endpoints: [None; 2],
second_endpoints: [None; 2],
flags: Vec::new(),
cache: cache.into_form()?,
discontinuities: tail_discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}));
} else {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = cur.take_f64()? * LEN_TO_MM;
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
let parameter_ranges = [
[cur.take_f64()? * LEN_TO_MM, cur.take_f64()? * LEN_TO_MM],
[cur.take_f64()? * LEN_TO_MM, cur.take_f64()? * LEN_TO_MM],
];
type_code = cur.take_long()?;
layout = Layout::Legacy {
cache_fit_tolerance,
discontinuities,
discontinuity_flag,
parameter_ranges,
};
}
if !matches!(cur.peek(), Some(Token::SubtypeOpen)) {
return None;
}
cur.bump();
let mut subtransform = propagate_resource!(t_spline_subtransform(&mut cur)?);
if let EmbeddedTSplineSubtransform::Reference { index, resolved } = &mut subtransform {
*resolved = propagate_resource!(usize::try_from(*index)
.ok()
.and_then(|index| resolve_t_spline_subtransform(
ctx,
index,
table,
&mut std::collections::HashSet::new()
))
.transpose());
}
if !matches!(cur.peek(), Some(Token::SubtypeClose)) {
return None;
}
cur.bump();
let trailing_value = cur.take_long()?;
cur.at_scope_end().then_some(())?;
let parameter_ranges = match &layout {
Layout::Legacy {
parameter_ranges, ..
} => *parameter_ranges,
Layout::Revision(form) => {
let bounds = form.support_bounds;
[
[bounds[0].unwrap_or(0.0), bounds[1].unwrap_or(0.0)],
[bounds[2].unwrap_or(0.0), bounds[3].unwrap_or(0.0)],
]
}
};
if parameter_ranges
.iter()
.any(|range| cadmpeg_ir::topology::ParameterInterval::new(*range).is_err())
{
return None;
}
Some(Ok(match layout {
Layout::Legacy {
cache_fit_tolerance,
discontinuities,
discontinuity_flag,
parameter_ranges,
} => DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::TSpline(Box::new(EmbeddedTSplineSurface {
parameter_ranges,
type_code,
subtransform,
trailing_value,
discontinuities,
discontinuity_flag,
revision_form: None,
})),
Some(cache_fit_tolerance),
),
Layout::Revision(form) => DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::TSpline(Box::new(EmbeddedTSplineSurface {
parameter_ranges,
type_code,
subtransform,
trailing_value,
discontinuities: copy_revision_discontinuities(&form.discontinuities),
discontinuity_flag: form.tail_flag,
revision_form: Some(*form),
})),
),
}))
}
pub(crate) fn copy_revision_discontinuities(source: &[Vec<f64>; 6]) -> [Vec<f64>; 6] {
[
source[0].clone(),
source[1].clone(),
source[2].clone(),
source[3].clone(),
source[4].clone(),
source[5].clone(),
]
}
fn deformable_surface_frame(
cur: &mut Cur<'_>,
) -> Option<cadmpeg_ir::geometry::DeformableSurfaceFrame> {
let mut leading_vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut leading_vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let leading_parameter = cur.take_f64()?;
let leading_flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
let mut secondary_vectors = [Vector3::new(0.0, 0.0, 0.0); 3];
for vector in &mut secondary_vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let secondary_parameter = cur.take_f64()?;
let secondary_flags = [cur.take_bool()?, cur.take_bool()?];
let point = cur.take_position()?;
let trailing_flags = [
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
];
Some(cadmpeg_ir::geometry::DeformableSurfaceFrame {
leading_vectors,
leading_parameter,
leading_flags,
secondary_vectors,
secondary_parameter,
secondary_flags,
point: Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
),
trailing_flags,
})
}
fn deformable_vector_frame(
cur: &mut Cur<'_>,
) -> Option<cadmpeg_ir::geometry::DeformableVectorFrame> {
let mut vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
Some(cadmpeg_ir::geometry::DeformableVectorFrame {
vectors,
parameter: cur.take_f64()?,
flags: [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?],
})
}
fn revision_deformable_mode3(
cur: &mut Cur<'_>,
) -> Option<cadmpeg_ir::geometry::DeformableSurfaceData> {
let mut leading_vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut leading_vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let leading_parameter = cur.take_f64()?;
let leading_flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
let point = cur.take_position()?;
let trailing_point = Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
);
let mut trailing_vectors = [Vector3::new(0.0, 0.0, 0.0); 2];
for vector in &mut trailing_vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let frame_parameter = cur.take_f64()?;
let frame_flags = [cur.take_bool()?, cur.take_bool()?];
let parameters = [cur.take_f64()?, cur.take_f64()?, cur.take_f64()?];
let trailing_flags = [
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
];
let trailing_parameter = cur.take_f64()?;
let trailing_value = cur.take_long()?;
Some(cadmpeg_ir::geometry::DeformableSurfaceData::RevisionMode3 {
leading_vectors,
leading_parameter,
leading_flags,
trailing_point,
trailing_vectors,
frame_parameter,
frame_flags,
parameters,
trailing_flags,
trailing_parameter,
trailing_value,
})
}
fn defm_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::DeformableSurfaceData;
let names = ["defm_spl_sur", "defmsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let (support, revision_form_head) = if matches!(cur.peek(), Some(Token::Long(_))) {
let revision = PositiveI64::new(cur.take_long()?)?;
(revision.get() == 22_506).then_some(())?;
let crate::nurbs::proc_curve::EmbeddedSupport {
surface: support,
ranges,
..
} = embedded_surface_with_ranges(&mut cur)?;
let support_bounds = [ranges[0][0], ranges[0][1], ranges[1][0], ranges[1][1]];
(support, Some((revision, support_bounds)))
} else {
(embedded_surface(&mut cur)?, None)
};
let mode = cur.take_long()?;
let data = match mode {
1 => {
let frame = Box::new(deformable_surface_frame(&mut cur)?);
let count = usize::try_from(cur.take_long()?).ok()?;
let mut parameter_triples = Vec::new();
for _ in 0..count {
parameter_triples.push([cur.take_f64()?, cur.take_f64()?, cur.take_f64()?]);
}
EmbeddedDeformableSurfaceData::Resolved(DeformableSurfaceData::Plain {
frame,
parameter_triples,
})
}
3 if revision_form_head.is_some() => {
EmbeddedDeformableSurfaceData::Resolved(revision_deformable_mode3(&mut cur)?)
}
3 => EmbeddedDeformableSurfaceData::Resolved(DeformableSurfaceData::Guided {
frame: Box::new(deformable_surface_frame(&mut cur)?),
selector: cur.take_long()?,
guide_parameter: cur.take_f64()?,
}),
5 => {
let surface = embedded_surface(&mut cur)?;
let native_id = cur.take_long()?;
let flag = cur.take_bool()?;
let first_parameter = cur.take_f64()?;
let selector = cur.take_long()?;
let second_parameter = cur.take_f64()?;
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
let mut vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let frame_parameter = cur.take_f64()?;
let flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
let count = usize::try_from(cur.take_long()?).ok()?;
let mut parameter_triples = Vec::new();
for _ in 0..count {
parameter_triples.push([cur.take_f64()?, cur.take_f64()?, cur.take_f64()?]);
}
EmbeddedDeformableSurfaceData::SurfaceCurve {
surface,
native_id,
flag,
first_parameter,
selector,
second_parameter,
curve,
vectors,
frame_parameter,
flags,
parameter_triples,
}
}
6 => {
let mut leading_vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut leading_vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let leading_parameter = cur.take_f64()?;
let leading_flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
let selector = cur.take_long()?;
let surface = embedded_surface(&mut cur)?;
let native_id = cur.take_long()?;
let flag = cur.take_bool()?;
let first_parameter = cur.take_f64()?;
let version_value = matches!(cur.peek(), Some(Token::Long(_)))
.then(|| cur.take_long())
.flatten();
let second_parameter = cur.take_f64()?;
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
let frames = Box::new([
deformable_vector_frame(&mut cur)?,
deformable_vector_frame(&mut cur)?,
]);
EmbeddedDeformableSurfaceData::Full {
leading_vectors,
leading_parameter,
leading_flags,
selector,
surface,
native_id,
flag,
first_parameter,
version_value,
second_parameter,
curve,
frames,
trailing_value: cur.take_long()?,
}
}
8 => {
let mut vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
EmbeddedDeformableSurfaceData::Resolved(DeformableSurfaceData::Minimal {
vectors,
selector: cur.take_long()?,
})
}
_ => return None,
};
if let Some((revision, support_bounds)) = revision_form_head {
let RevisionSurfaceTail {
cache,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
Some(DecodedProceduralSurface::revision(
DecodedProceduralSurfaceDefinition::Deformable(Box::new(EmbeddedDeformableSurface {
support,
layout: EmbeddedDeformableSurfaceLayout::Revision(Box::new(
cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds,
reference_endpoints: [None; 2],
second_endpoints: [None; 2],
flags: Vec::new(),
cache: cache.into_form()?,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
},
)),
data,
})),
))
} else {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
Some(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Deformable(Box::new(EmbeddedDeformableSurface {
support,
layout: EmbeddedDeformableSurfaceLayout::Legacy {
discontinuities,
discontinuity_flag,
},
data,
})),
cache_fit_tolerance,
))
}
}
pub(super) fn helix_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::HelixSurfaceProfile;
let names = ["helix_spl_circ", "helix_spl_line"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let circular = name == "helix_spl_circ";
let span = toks::subtype_span(toks, start)?.tokens();
let mut cur = Cur::at(span, 2);
let current_layout = optional_helix_revision(&mut cur)?;
let angle_range = [cur.take_range_value()?, cur.take_range_value()?];
let dimension_scale = if circular { LEN_TO_MM } else { 1.0 };
let dimension_range = [
cur.take_range_value()? * dimension_scale,
cur.take_range_value()? * dimension_scale,
];
let length = circular
.then(|| cur.take_f64().map(|v| v * LEN_TO_MM))
.flatten();
let path_angle_range = [cur.take_range_value()?, cur.take_range_value()?];
let center = cur.take_position()?;
let take_frame_vector = |cur: &mut Cur<'_>| {
if current_layout {
cur.take_vector3()
} else {
cur.take_position()
}
};
let major = take_frame_vector(&mut cur)?;
let minor = take_frame_vector(&mut cur)?;
let pitch = take_frame_vector(&mut cur)?;
let apex_factor = cur.take_f64()?;
let axis = normalized(cur.take_vector3()?)?;
for sentinel in ["null_surface", "null_surface", "nullbs", "nullbs"] {
if cur.take_ident()? != sentinel {
return None;
}
}
let path = cadmpeg_ir::geometry::HelixPathConstruction::try_with_unit_axis(
path_angle_range,
cadmpeg_ir::geometry::HelixFrame {
center: Point3::new(
center[0] * LEN_TO_MM,
center[1] * LEN_TO_MM,
center[2] * LEN_TO_MM,
),
major: Vector3::new(
major[0] * LEN_TO_MM,
major[1] * LEN_TO_MM,
major[2] * LEN_TO_MM,
),
minor: Vector3::new(
minor[0] * LEN_TO_MM,
minor[1] * LEN_TO_MM,
minor[2] * LEN_TO_MM,
),
pitch: Vector3::new(
pitch[0] * LEN_TO_MM,
pitch[1] * LEN_TO_MM,
pitch[2] * LEN_TO_MM,
),
axis,
},
apex_factor,
)
.ok()?;
let profile = if let Some(length) = length {
HelixSurfaceProfile::Circle(
cadmpeg_ir::geometry::HelixCircleProfile::try_new(length, cur.take_f64()? * LEN_TO_MM)
.ok()?,
)
} else {
let direction = take_frame_vector(&mut cur)?;
HelixSurfaceProfile::Line(
cadmpeg_ir::geometry::HelixLineProfile::try_new(Vector3::new(
direction[0] * LEN_TO_MM,
direction[1] * LEN_TO_MM,
direction[2] * LEN_TO_MM,
))
.ok()?,
)
};
Some(DecodedProceduralSurface::legacy(
DecodedProceduralSurfaceDefinition::Helix(Box::new(
cadmpeg_ir::geometry::HelixSurfaceConstruction::try_new(
angle_range,
dimension_range,
path,
profile,
)
.ok()?,
)),
None,
))
}
fn t_spline_subtransform(
cur: &mut Cur<'_>,
) -> Option<Result<EmbeddedTSplineSubtransform, cadmpeg_core::CodecError>> {
match cur.take_ident()? {
"t_spl_subtrans_object" => {
let program_text = cur.take_str()?;
let program = program_text.to_owned();
let separator = if matches!(cur.peek(), Some(Token::Str(_))) {
None
} else {
Some(cur.take_bool()?)
};
let values_text = cur.take_str()?;
let values = values_text.to_owned();
Some(Ok(EmbeddedTSplineSubtransform::Inline {
program,
separator,
values,
}))
}
"ref" => Some(Ok(EmbeddedTSplineSubtransform::Reference {
index: cur.take_long()?,
resolved: None,
})),
_ => None,
}
}
fn resolve_t_spline_subtransform(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
index: usize,
table: &SubtypeTable,
seen: &mut std::collections::HashSet<usize>,
) -> Option<Result<cadmpeg_ir::geometry::InlineTSplineSubtransform, cadmpeg_core::CodecError>> {
let _depth = propagate_resource!(ctx.enter_nested("resolve ASM t spline subtransform"));
if seen.contains(&index) {
return None;
}
seen.insert(index);
let span = table.span(index)?.tokens();
let start = usize::from(matches!(span.first(), Some(Token::SubtypeOpen)));
let decoded = propagate_resource!(t_spline_subtransform(&mut Cur::at(span, start))?);
match decoded {
EmbeddedTSplineSubtransform::Inline {
program,
separator,
values,
} => cadmpeg_ir::geometry::InlineTSplineSubtransform::try_new(program, separator, values)
.ok()
.map(Ok),
EmbeddedTSplineSubtransform::Reference { index, .. } => {
resolve_t_spline_subtransform(ctx, usize::try_from(index).ok()?, table, seen)
}
}
}
pub fn procedural_surface_resolving_refs(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
procedural_resolving_refs(ctx, toks, table, &mut std::collections::HashSet::new())
}
fn procedural_resolving_refs(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
seen: &mut std::collections::HashSet<usize>,
) -> Option<Result<DecodedProceduralSurface, cadmpeg_core::CodecError>> {
let _depth = propagate_resource!(ctx.enter_nested("resolve ASM procedural surface reference"));
if let Some(decoded) = defm_spl_sur(toks)
.map(Ok)
.or_else(|| helix_spl_sur(toks).map(Ok))
.or_else(|| t_spl_sur(ctx, toks, table))
.or_else(|| exact_spl_sur(toks).map(Ok))
.or_else(|| comp_spl_sur(toks).map(Ok))
.or_else(|| taper_spl_sur(ctx, toks, Some(table)))
.or_else(|| loft_spl_sur(ctx, toks, Some(table)))
.or_else(|| compound_loft_spl_sur(ctx, toks, Some(table)))
.or_else(|| scaled_compound_loft_spl_sur(toks).map(Ok))
.or_else(|| sub_spl_sur(toks).map(Ok))
.or_else(|| law_spl_sur(ctx, toks))
.or_else(|| skin_spl_sur(ctx, toks))
.or_else(|| net_spl_sur(ctx, toks))
.or_else(|| sweep_spl_sur(ctx, toks, Some(table)))
.or_else(|| g2_blend_spl_sur(ctx, toks, Some(table)))
.or_else(|| ruled_spl_sur(toks).map(Ok))
.or_else(|| sum_spl_sur(toks, Some(table)).map(Ok))
.or_else(|| rot_spl_sur(toks, Some(table)).map(Ok))
.or_else(|| off_spl_sur(ctx, toks, Some(table)))
.or_else(|| cyl_spl_sur(toks, Some(table)).map(Ok))
.or_else(|| {
var_blend_spl_sur(ctx, toks, Some(table), SupportCaches::Resolve)
.and_then(|result| result.map(|parsed| parsed.surface).transpose())
})
.or_else(|| vertex_blend_spl_sur(ctx, toks, Some(table)))
.or_else(|| full_rb_blend_spl_sur(ctx, toks, table))
.or_else(|| compact_rb_blend_spl_sur(toks).map(Ok))
{
return Some(decoded);
}
if toks::owned_subtype_defs(toks)?
.iter()
.any(|(_, name)| *name != "ref")
{
return None;
}
for index in toks::subtype_refs(toks) {
if seen.contains(&index) {
continue;
}
let target = table.span(index)?.tokens();
seen.insert(index);
if let Some(decoded) = procedural_resolving_refs(ctx, target, table, seen) {
return Some(decoded);
}
}
None
}
#[cfg(test)]
mod reference_allocation_tests {}
#[cfg(test)]
mod loft_count_tests {
use super::loft_subdata_form;
use crate::nurbs::toks::Cur;
use crate::sab::Token;
#[test]
fn huge_column_count_without_pairs_is_refused_before_reservation() {
let tokens = [
Token::Long(212),
Token::Long(1),
Token::Long(i64::MAX),
Token::Double(0.0),
Token::Double(1.0),
];
assert!(loft_subdata_form(&mut Cur::at(&tokens, 0), false).is_none());
}
}
#[cfg(test)]
mod sweep_law_tests {
use super::{
law_expression, law_formula_resolving, sweep_law_expression, EmbeddedLawExpression,
};
use crate::nurbs::toks::Cur;
use crate::sab::Token;
use cadmpeg_ir::math::Vector3;
#[test]
fn sweep_text_law_consumes_one_serializer_token() {
let resource_arena = cadmpeg_core::decode::DecodeArena::new();
let (resource_ctx, _) = cadmpeg_core::decode::DecodeContext::from_root_bytes(
&[],
&resource_arena,
&cadmpeg_core::decode::DecodePolicy::default(),
)
.expect("test decode context");
let tokens = [Token::Str("0.008726867790758789*X".into()), Token::Long(21)];
let mut cur = Cur::at(&tokens, 0);
let law = sweep_law_expression(&resource_ctx, &mut cur)
.transpose()
.expect("decode the sweep law expression")
.expect("text law");
let EmbeddedLawExpression::Text(value) = law else {
panic!("expected text law");
};
assert_eq!(value, "0.008726867790758789*X");
assert_eq!(cur.take_long(), Some(21));
assert_eq!(cur.pos(), tokens.len());
}
#[test]
fn composition_law_consumes_two_recursive_operands() {
let resource_arena = cadmpeg_core::decode::DecodeArena::new();
let (resource_ctx, _) = cadmpeg_core::decode::DecodeContext::from_root_bytes(
&[],
&resource_arena,
&cadmpeg_core::decode::DecodePolicy::default(),
)
.expect("test decode context");
let tokens = [
Token::Str("O".into()),
Token::Str("ABS".into()),
Token::Double(-2.5),
Token::Str("SIN".into()),
Token::Double(0.25),
];
let mut cur = Cur::at(&tokens, 0);
let law = law_expression(&resource_ctx, &mut cur, 0)
.transpose()
.expect("decode the law expression")
.expect("composition law");
assert!(matches!(
law,
EmbeddedLawExpression::Algebraic { operator, operands }
if operator == "O"
&& matches!(operands.as_slice(), [
EmbeddedLawExpression::Algebraic { operator: left, operands: left_operands },
EmbeddedLawExpression::Algebraic { operator: right, operands: right_operands },
] if left == "ABS"
&& matches!(left_operands.as_slice(), [EmbeddedLawExpression::Double(value)] if *value == -2.5)
&& right == "SIN"
&& matches!(right_operands.as_slice(), [EmbeddedLawExpression::Double(value)] if *value == 0.25))
));
assert_eq!(cur.pos(), tokens.len());
}
#[test]
fn rail_formula_decodes_counted_vector_transform_binding() {
let resource_arena = cadmpeg_core::decode::DecodeArena::new();
let (resource_ctx, _) = cadmpeg_core::decode::DecodeContext::from_root_bytes(
&[],
&resource_arena,
&cadmpeg_core::decode::DecodePolicy::default(),
)
.expect("test decode context");
let vectors = [
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[2.0, 3.0, 4.0],
];
let mut tokens = vec![
Token::Str("ROTATE(DOMAIN(VEC(1,0,0),0,0.8),TRANS1)".into()),
Token::Long(1),
Token::Str("TRANS".into()),
];
tokens.extend(vectors.into_iter().map(Token::Vector3));
tokens.extend([Token::Double(1.5), Token::True, Token::False, Token::True]);
let mut cur = Cur::at(&tokens, 0);
let formula = law_formula_resolving(&resource_ctx, &mut cur, None)
.transpose()
.expect("decode the law formula")
.expect("rail formula");
assert_eq!(formula.name(), "ROTATE(DOMAIN(VEC(1,0,0),0,0.8),TRANS1)");
let [EmbeddedLawExpression::TransformVec {
vectors: actual_vectors,
scale,
flags,
}] = formula.variables()
else {
panic!("expected one vector transform binding");
};
assert_eq!(
*actual_vectors,
vectors.map(|value| Vector3::new(value[0], value[1], value[2]))
);
assert_eq!(*scale, 1.5);
assert_eq!(*flags, [true, false, true]);
assert_eq!(cur.pos(), tokens.len());
}
}
#[cfg(test)]
mod tail_selector_tests {
use super::{revision_surface_tail, RevisionSurfaceCache};
use crate::kernel_header::RefWidth;
use crate::nurbs::toks::{self, Cur};
fn push_enum(span: &mut Vec<u8>, value: i32) {
span.push(0x15);
span.extend_from_slice(&value.to_le_bytes());
}
fn push_float_array(span: &mut Vec<u8>, count: i32) {
span.push(0x04);
span.extend_from_slice(&count.to_le_bytes());
for _ in 0..count {
span.push(0x06);
span.extend_from_slice(&0.0f64.to_le_bytes());
}
}
#[test]
fn undefined_tail_form_is_rejected_for_verbatim_retention() {
let mut span = Vec::new();
push_enum(&mut span, 1);
span.push(0x06);
span.extend_from_slice(&0.0f64.to_le_bytes());
let toks =
toks::lex_test_span(&span, RefWidth::Four).expect("valid single-record byte fixture");
let mut cur = Cur::at(&toks, 0);
assert!(revision_surface_tail(&mut cur).is_none());
}
#[test]
fn parameterized_tail_form_decodes_intervals_then_closure_enums() {
let mut span = Vec::new();
push_enum(&mut span, 2);
span.push(0x0a);
span.push(0x06);
span.extend_from_slice(&0.25f64.to_le_bytes());
span.push(0x0b);
for value in [(-1.5f64), 3.5] {
span.push(0x0a);
span.push(0x06);
span.extend_from_slice(&value.to_le_bytes());
}
for value in [1, 0, 2, 3] {
push_enum(&mut span, value);
}
push_float_array(&mut span, 1);
for _ in 0..5 {
push_float_array(&mut span, 0);
}
span.push(0x0b);
let toks =
toks::lex_test_span(&span, RefWidth::Four).expect("valid single-record byte fixture");
let mut cur = Cur::at(&toks, 0);
let tail = revision_surface_tail(&mut cur).expect("parameterized tail");
assert_eq!(cur.pos(), toks.len());
let RevisionSurfaceCache::Parameterized(parameterization) = tail.cache else {
panic!("parameterization");
};
assert_eq!(parameterization.u_interval, [Some(0.25), None]);
assert_eq!(parameterization.v_interval, [Some(-1.5), Some(3.5)]);
assert_eq!(parameterization.u_closure, 1);
assert_eq!(parameterization.v_closure, 0);
assert_eq!(parameterization.u_singularity, 2);
assert_eq!(parameterization.v_singularity, 3);
assert_eq!(tail.discontinuities[0], [0.0]);
assert!(tail.discontinuities[1..].iter().all(Vec::is_empty));
assert!(!tail.tail_flag);
}
}
#[cfg(test)]
mod ellipse_tests {
#[test]
fn numerical_followup_ellipse_accepts_extreme_finite_radii() {
for radius in [1.0, 1e200, 1e-200] {
let curve =
super::ellipse_to_nurbs([0.; 3], [0., 0., 1.], [radius, 0., 0.], 0.5).unwrap();
let poles = curve.control_points();
assert_eq!(poles[0].x, radius * super::LEN_TO_MM);
assert_eq!(poles[2].y, 0.5 * radius * super::LEN_TO_MM);
}
}
}