use std::fmt;
use super::parser::SatParser;
use super::writer::SatWriter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SatVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl SatVersion {
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
Self { major, minor, patch }
}
pub const V4_0: Self = Self { major: 4, minor: 0, patch: 0 };
pub const V7_0: Self = Self { major: 7, minor: 0, patch: 0 };
pub const V21_0: Self = Self { major: 21, minor: 0, patch: 0 };
pub fn sat_version_number(&self) -> u32 {
self.major * 100 + self.minor * 10 + self.patch
}
pub fn from_sat_number(num: u32) -> Self {
Self {
major: num / 100,
minor: (num % 100) / 10,
patch: num % 10,
}
}
pub fn has_explicit_indices(&self) -> bool {
self.major >= 7
}
pub fn has_counted_strings(&self) -> bool {
self.major >= 7
}
pub fn has_asm_header(&self) -> bool {
self.major >= 7
}
}
impl Default for SatVersion {
fn default() -> Self {
Self::V7_0
}
}
impl fmt::Display for SatVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SatHeader {
pub version: SatVersion,
pub num_records: usize,
pub num_bodies: usize,
pub has_history: bool,
pub product_id: String,
pub product_version: String,
pub date: String,
pub spatial_resolution: f64,
pub normal_tolerance: f64,
pub resfit_tolerance: Option<f64>,
}
impl SatHeader {
pub fn new() -> Self {
Self {
version: SatVersion::V7_0,
num_records: 0,
num_bodies: 0,
has_history: false,
product_id: "acadrust".to_string(),
product_version: "ACIS 7.0".to_string(),
date: "Thu Jan 01 00:00:00 2023".to_string(),
spatial_resolution: 10.0,
normal_tolerance: 9.9999999999999995e-07,
resfit_tolerance: Some(1e-10),
}
}
pub fn with_version(version: SatVersion) -> Self {
let mut header = Self::new();
header.version = version;
header.product_version = format!("ACIS {}.{}", version.major, version.minor);
header
}
}
impl Default for SatHeader {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SatPointer(pub i32);
impl SatPointer {
pub const NULL: Self = Self(-1);
pub fn new(index: i32) -> Self {
Self(index)
}
pub fn is_null(&self) -> bool {
self.0 < 0
}
pub fn index(&self) -> Option<usize> {
if self.0 >= 0 {
Some(self.0 as usize)
} else {
None
}
}
}
impl Default for SatPointer {
fn default() -> Self {
Self::NULL
}
}
impl fmt::Display for SatPointer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "${}", self.0)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SatToken {
Ident(String),
Pointer(SatPointer),
Integer(i64),
Float(f64),
String(String),
Position(f64, f64, f64),
True,
False,
Terminator,
Enum(String),
}
impl SatToken {
pub fn as_ident(&self) -> Option<&str> {
match self {
SatToken::Ident(s) | SatToken::Enum(s) => Some(s),
_ => None,
}
}
pub fn as_pointer(&self) -> Option<SatPointer> {
match self {
SatToken::Pointer(p) => Some(*p),
_ => None,
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
SatToken::Integer(v) => Some(*v),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
SatToken::Float(v) => Some(*v),
SatToken::Integer(v) => Some(*v as f64),
_ => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match self {
SatToken::String(s) => Some(s),
_ => None,
}
}
}
impl fmt::Display for SatToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SatToken::Ident(s) => write!(f, "{}", s),
SatToken::Pointer(p) => write!(f, "{}", p),
SatToken::Integer(v) => write!(f, "{}", v),
SatToken::Float(v) => {
if v.fract() == 0.0 && !v.is_infinite() && !v.is_nan() {
write!(f, "{:.1}", v)
} else {
write!(f, "{}", v)
}
}
SatToken::String(s) => write!(f, "@{} {}", s.len(), s),
SatToken::Position(x, y, z) => write!(f, "{} {} {}", x, y, z),
SatToken::True => write!(f, "TRUE"),
SatToken::False => write!(f, "FALSE"),
SatToken::Terminator => write!(f, "#"),
SatToken::Enum(s) => write!(f, "{}", s),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SatRecord {
pub index: i32,
pub entity_type: String,
pub sub_type: Option<String>,
pub attribute: SatPointer,
pub subtype_id: i32,
pub tokens: Vec<SatToken>,
pub raw_text: Option<String>,
}
impl SatRecord {
pub fn new(index: i32, entity_type: &str) -> Self {
Self {
index,
entity_type: entity_type.to_string(),
sub_type: None,
attribute: SatPointer::NULL,
subtype_id: -1,
tokens: Vec::new(),
raw_text: None,
}
}
pub fn pointers(&self) -> Vec<SatPointer> {
let mut ptrs = vec![self.attribute];
for token in &self.tokens {
if let SatToken::Pointer(p) = token {
ptrs.push(*p);
}
}
ptrs
}
pub fn token(&self, index: usize) -> Option<&SatToken> {
self.tokens.get(index)
}
pub fn token_integer(&self, index: usize) -> Option<i64> {
self.tokens.get(index).and_then(|t| t.as_integer())
}
pub fn token_float(&self, index: usize) -> Option<f64> {
self.tokens.get(index).and_then(|t| t.as_float())
}
pub fn token_pointer(&self, index: usize) -> Option<SatPointer> {
self.tokens.get(index).and_then(|t| t.as_pointer())
}
pub fn token_string(&self, index: usize) -> Option<&str> {
self.tokens.get(index).and_then(|t| match t {
SatToken::String(s) | SatToken::Ident(s) | SatToken::Enum(s) => Some(s.as_str()),
_ => None,
})
}
}
impl fmt::Display for SatRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.index, self.entity_type, self.attribute)?;
for token in &self.tokens {
write!(f, " {}", token)?;
}
write!(f, " #")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Sense {
Forward,
Reversed,
}
impl Sense {
pub fn from_str(s: &str) -> Self {
match s {
"reversed" | "REVERSED" => Self::Reversed,
_ => Self::Forward,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Forward => "forward",
Self::Reversed => "reversed",
}
}
}
impl Default for Sense {
fn default() -> Self {
Self::Forward
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Sidedness {
Single,
Double,
}
impl Sidedness {
pub fn from_str(s: &str) -> Self {
match s {
"double" | "DOUBLE" => Self::Double,
_ => Self::Single,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Single => "single",
Self::Double => "double",
}
}
}
impl Default for Sidedness {
fn default() -> Self {
Self::Single
}
}
#[derive(Debug, Clone)]
pub struct SatBody<'a> {
record: &'a SatRecord,
}
impl<'a> SatBody<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "body" {
Some(Self { record })
} else {
None
}
}
pub fn next_body(&self) -> SatPointer {
self.record.token_pointer(0).unwrap_or(SatPointer::NULL)
}
pub fn lump(&self) -> SatPointer {
self.record.token_pointer(1).unwrap_or(SatPointer::NULL)
}
pub fn wire_body(&self) -> SatPointer {
self.record.token_pointer(2).unwrap_or(SatPointer::NULL)
}
pub fn transform(&self) -> SatPointer {
self.record.token_pointer(3).unwrap_or(SatPointer::NULL)
}
}
#[derive(Debug, Clone)]
pub struct SatLump<'a> {
record: &'a SatRecord,
}
impl<'a> SatLump<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "lump" {
Some(Self { record })
} else {
None
}
}
pub fn next_lump(&self) -> SatPointer {
self.record.token_pointer(0).unwrap_or(SatPointer::NULL)
}
pub fn shell(&self) -> SatPointer {
self.record.token_pointer(2).unwrap_or(SatPointer::NULL)
}
pub fn body(&self) -> SatPointer {
self.record.token_pointer(3).unwrap_or(SatPointer::NULL)
}
}
#[derive(Debug, Clone)]
pub struct SatShell<'a> {
record: &'a SatRecord,
}
impl<'a> SatShell<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "shell" {
Some(Self { record })
} else {
None
}
}
pub fn next_shell(&self) -> SatPointer {
self.record.token_pointer(0).unwrap_or(SatPointer::NULL)
}
pub fn subshell(&self) -> SatPointer {
self.record.token_pointer(1).unwrap_or(SatPointer::NULL)
}
pub fn face(&self) -> SatPointer {
self.record.token_pointer(3).unwrap_or(SatPointer::NULL)
}
pub fn wire(&self) -> SatPointer {
self.record.token_pointer(4).unwrap_or(SatPointer::NULL)
}
pub fn lump(&self) -> SatPointer {
self.record.token_pointer(5).unwrap_or(SatPointer::NULL)
}
}
#[derive(Debug, Clone)]
pub struct SatFace<'a> {
record: &'a SatRecord,
}
impl<'a> SatFace<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "face" {
Some(Self { record })
} else {
None
}
}
pub fn next_face(&self) -> SatPointer {
self.record.token_pointer(1).unwrap_or(SatPointer::NULL)
}
pub fn first_loop(&self) -> SatPointer {
self.record.token_pointer(2).unwrap_or(SatPointer::NULL)
}
pub fn shell(&self) -> SatPointer {
self.record.token_pointer(3).unwrap_or(SatPointer::NULL)
}
pub fn subshell(&self) -> SatPointer {
self.record.token_pointer(4).unwrap_or(SatPointer::NULL)
}
pub fn surface(&self) -> SatPointer {
self.record.token_pointer(5).unwrap_or(SatPointer::NULL)
}
pub fn sense(&self) -> Sense {
self.record
.token_string(6)
.map(Sense::from_str)
.unwrap_or_default()
}
pub fn sidedness(&self) -> Sidedness {
self.record
.token_string(7)
.map(Sidedness::from_str)
.unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct SatLoop<'a> {
record: &'a SatRecord,
}
impl<'a> SatLoop<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "loop" {
Some(Self { record })
} else {
None
}
}
pub fn next_loop(&self) -> SatPointer {
self.record.token_pointer(0).unwrap_or(SatPointer::NULL)
}
pub fn first_coedge(&self) -> SatPointer {
self.record.token_pointer(2).unwrap_or(SatPointer::NULL)
}
pub fn face(&self) -> SatPointer {
self.record.token_pointer(3).unwrap_or(SatPointer::NULL)
}
}
#[derive(Debug, Clone)]
pub struct SatCoedge<'a> {
record: &'a SatRecord,
}
impl<'a> SatCoedge<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "coedge" {
Some(Self { record })
} else {
None
}
}
pub fn next(&self) -> SatPointer {
self.record.token_pointer(1).unwrap_or(SatPointer::NULL)
}
pub fn prev(&self) -> SatPointer {
self.record.token_pointer(2).unwrap_or(SatPointer::NULL)
}
pub fn partner(&self) -> SatPointer {
self.record.token_pointer(3).unwrap_or(SatPointer::NULL)
}
pub fn edge(&self) -> SatPointer {
self.record.token_pointer(4).unwrap_or(SatPointer::NULL)
}
pub fn sense(&self) -> Sense {
self.record
.token_string(5)
.map(Sense::from_str)
.unwrap_or_default()
}
pub fn owner_loop(&self) -> SatPointer {
self.record.token_pointer(6).unwrap_or(SatPointer::NULL)
}
}
#[derive(Debug, Clone)]
pub struct SatEdge<'a> {
record: &'a SatRecord,
}
impl<'a> SatEdge<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "edge" {
Some(Self { record })
} else {
None
}
}
pub fn start_vertex(&self) -> SatPointer {
self.record.token_pointer(1).unwrap_or(SatPointer::NULL)
}
pub fn start_param(&self) -> f64 {
self.record.token_float(2).unwrap_or(0.0)
}
pub fn end_vertex(&self) -> SatPointer {
self.record.token_pointer(3).unwrap_or(SatPointer::NULL)
}
pub fn end_param(&self) -> f64 {
self.record.token_float(4).unwrap_or(0.0)
}
pub fn coedge(&self) -> SatPointer {
self.record.token_pointer(5).unwrap_or(SatPointer::NULL)
}
pub fn curve(&self) -> SatPointer {
self.record.token_pointer(6).unwrap_or(SatPointer::NULL)
}
pub fn sense(&self) -> Sense {
self.record
.token_string(7)
.map(Sense::from_str)
.unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct SatVertex<'a> {
record: &'a SatRecord,
}
impl<'a> SatVertex<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "vertex" {
Some(Self { record })
} else {
None
}
}
pub fn edge(&self) -> SatPointer {
self.record.token_pointer(1).unwrap_or(SatPointer::NULL)
}
pub fn point(&self) -> SatPointer {
self.record.token_pointer(2).unwrap_or(SatPointer::NULL)
}
}
#[derive(Debug, Clone)]
pub struct SatPoint<'a> {
record: &'a SatRecord,
}
impl<'a> SatPoint<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "point" {
Some(Self { record })
} else {
None
}
}
pub fn position(&self) -> (f64, f64, f64) {
let x = self.record.token_float(1).unwrap_or(0.0);
let y = self.record.token_float(2).unwrap_or(0.0);
let z = self.record.token_float(3).unwrap_or(0.0);
(x, y, z)
}
}
#[derive(Debug, Clone)]
pub struct SatStraightCurve<'a> {
record: &'a SatRecord,
}
impl<'a> SatStraightCurve<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "straight-curve" {
Some(Self { record })
} else {
None
}
}
pub fn root_point(&self) -> (f64, f64, f64) {
let x = self.record.token_float(1).unwrap_or(0.0);
let y = self.record.token_float(2).unwrap_or(0.0);
let z = self.record.token_float(3).unwrap_or(0.0);
(x, y, z)
}
pub fn direction(&self) -> (f64, f64, f64) {
let x = self.record.token_float(4).unwrap_or(0.0);
let y = self.record.token_float(5).unwrap_or(0.0);
let z = self.record.token_float(6).unwrap_or(1.0);
(x, y, z)
}
}
#[derive(Debug, Clone)]
pub struct SatEllipseCurve<'a> {
record: &'a SatRecord,
}
impl<'a> SatEllipseCurve<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "ellipse-curve" {
Some(Self { record })
} else {
None
}
}
pub fn center(&self) -> (f64, f64, f64) {
let x = self.record.token_float(1).unwrap_or(0.0);
let y = self.record.token_float(2).unwrap_or(0.0);
let z = self.record.token_float(3).unwrap_or(0.0);
(x, y, z)
}
pub fn normal(&self) -> (f64, f64, f64) {
let x = self.record.token_float(4).unwrap_or(0.0);
let y = self.record.token_float(5).unwrap_or(0.0);
let z = self.record.token_float(6).unwrap_or(1.0);
(x, y, z)
}
pub fn major_axis(&self) -> (f64, f64, f64) {
let x = self.record.token_float(7).unwrap_or(1.0);
let y = self.record.token_float(8).unwrap_or(0.0);
let z = self.record.token_float(9).unwrap_or(0.0);
(x, y, z)
}
pub fn ratio(&self) -> f64 {
self.record.token_float(10).unwrap_or(1.0)
}
}
#[derive(Debug, Clone)]
pub struct SatPlaneSurface<'a> {
record: &'a SatRecord,
}
impl<'a> SatPlaneSurface<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "plane-surface" {
Some(Self { record })
} else {
None
}
}
pub fn root_point(&self) -> (f64, f64, f64) {
let x = self.record.token_float(1).unwrap_or(0.0);
let y = self.record.token_float(2).unwrap_or(0.0);
let z = self.record.token_float(3).unwrap_or(0.0);
(x, y, z)
}
pub fn normal(&self) -> (f64, f64, f64) {
let x = self.record.token_float(4).unwrap_or(0.0);
let y = self.record.token_float(5).unwrap_or(0.0);
let z = self.record.token_float(6).unwrap_or(1.0);
(x, y, z)
}
pub fn u_direction(&self) -> (f64, f64, f64) {
let x = self.record.token_float(7).unwrap_or(1.0);
let y = self.record.token_float(8).unwrap_or(0.0);
let z = self.record.token_float(9).unwrap_or(0.0);
(x, y, z)
}
}
#[derive(Debug, Clone)]
pub struct SatConeSurface<'a> {
record: &'a SatRecord,
}
impl<'a> SatConeSurface<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "cone-surface" {
Some(Self { record })
} else {
None
}
}
pub fn center(&self) -> (f64, f64, f64) {
let x = self.record.token_float(1).unwrap_or(0.0);
let y = self.record.token_float(2).unwrap_or(0.0);
let z = self.record.token_float(3).unwrap_or(0.0);
(x, y, z)
}
pub fn axis(&self) -> (f64, f64, f64) {
let x = self.record.token_float(4).unwrap_or(0.0);
let y = self.record.token_float(5).unwrap_or(0.0);
let z = self.record.token_float(6).unwrap_or(1.0);
(x, y, z)
}
pub fn major_axis(&self) -> (f64, f64, f64) {
let x = self.record.token_float(7).unwrap_or(1.0);
let y = self.record.token_float(8).unwrap_or(0.0);
let z = self.record.token_float(9).unwrap_or(0.0);
(x, y, z)
}
pub fn ratio(&self) -> f64 {
self.record.token_float(10).unwrap_or(1.0)
}
pub fn sin_half_angle(&self) -> f64 {
self.record.token_float(13).unwrap_or(0.0)
}
pub fn cos_half_angle(&self) -> f64 {
self.record.token_float(14).unwrap_or(1.0)
}
pub fn radius(&self) -> f64 {
self.record.token_float(15).unwrap_or(1.0)
}
}
#[derive(Debug, Clone)]
pub struct SatSphereSurface<'a> {
record: &'a SatRecord,
}
impl<'a> SatSphereSurface<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "sphere-surface" {
Some(Self { record })
} else {
None
}
}
pub fn center(&self) -> (f64, f64, f64) {
let x = self.record.token_float(1).unwrap_or(0.0);
let y = self.record.token_float(2).unwrap_or(0.0);
let z = self.record.token_float(3).unwrap_or(0.0);
(x, y, z)
}
pub fn radius(&self) -> f64 {
self.record.token_float(4).unwrap_or(1.0)
}
pub fn u_direction(&self) -> (f64, f64, f64) {
let x = self.record.token_float(5).unwrap_or(1.0);
let y = self.record.token_float(6).unwrap_or(0.0);
let z = self.record.token_float(7).unwrap_or(0.0);
(x, y, z)
}
pub fn pole(&self) -> (f64, f64, f64) {
let x = self.record.token_float(8).unwrap_or(0.0);
let y = self.record.token_float(9).unwrap_or(0.0);
let z = self.record.token_float(10).unwrap_or(1.0);
(x, y, z)
}
}
#[derive(Debug, Clone)]
pub struct SatTorusSurface<'a> {
record: &'a SatRecord,
}
impl<'a> SatTorusSurface<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "torus-surface" {
Some(Self { record })
} else {
None
}
}
pub fn center(&self) -> (f64, f64, f64) {
let x = self.record.token_float(1).unwrap_or(0.0);
let y = self.record.token_float(2).unwrap_or(0.0);
let z = self.record.token_float(3).unwrap_or(0.0);
(x, y, z)
}
pub fn normal(&self) -> (f64, f64, f64) {
let x = self.record.token_float(4).unwrap_or(0.0);
let y = self.record.token_float(5).unwrap_or(0.0);
let z = self.record.token_float(6).unwrap_or(1.0);
(x, y, z)
}
pub fn major_radius(&self) -> f64 {
self.record.token_float(7).unwrap_or(1.0)
}
pub fn minor_radius(&self) -> f64 {
self.record.token_float(8).unwrap_or(0.5)
}
pub fn u_direction(&self) -> (f64, f64, f64) {
let x = self.record.token_float(9).unwrap_or(1.0);
let y = self.record.token_float(10).unwrap_or(0.0);
let z = self.record.token_float(11).unwrap_or(0.0);
(x, y, z)
}
}
#[derive(Debug, Clone)]
pub struct SatTransform<'a> {
record: &'a SatRecord,
}
impl<'a> SatTransform<'a> {
pub fn from_record(record: &'a SatRecord) -> Option<Self> {
if record.entity_type == "transform" {
Some(Self { record })
} else {
None
}
}
pub fn row0(&self) -> (f64, f64, f64) {
(
self.record.token_float(1).unwrap_or(1.0),
self.record.token_float(2).unwrap_or(0.0),
self.record.token_float(3).unwrap_or(0.0),
)
}
pub fn row1(&self) -> (f64, f64, f64) {
(
self.record.token_float(4).unwrap_or(0.0),
self.record.token_float(5).unwrap_or(1.0),
self.record.token_float(6).unwrap_or(0.0),
)
}
pub fn row2(&self) -> (f64, f64, f64) {
(
self.record.token_float(7).unwrap_or(0.0),
self.record.token_float(8).unwrap_or(0.0),
self.record.token_float(9).unwrap_or(1.0),
)
}
pub fn translation(&self) -> (f64, f64, f64) {
(
self.record.token_float(10).unwrap_or(0.0),
self.record.token_float(11).unwrap_or(0.0),
self.record.token_float(12).unwrap_or(0.0),
)
}
pub fn scale(&self) -> f64 {
self.record.token_float(13).unwrap_or(1.0)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SatDocument {
pub header: SatHeader,
pub records: Vec<SatRecord>,
}
impl SatDocument {
pub fn new() -> Self {
Self {
header: SatHeader::new(),
records: Vec::new(),
}
}
pub fn with_header(header: SatHeader) -> Self {
Self {
header,
records: Vec::new(),
}
}
pub fn parse(text: &str) -> Result<Self, SatParseError> {
SatParser::parse(text)
}
pub fn to_sat_string(&self) -> String {
SatWriter::write(self)
}
pub fn record_count(&self) -> usize {
self.records.len()
}
pub fn record(&self, index: usize) -> Option<&SatRecord> {
self.records.iter().find(|r| r.index == index as i32)
}
pub fn record_mut(&mut self, index: usize) -> Option<&mut SatRecord> {
self.records.iter_mut().find(|r| r.index == index as i32)
}
pub fn records_of_type(&self, entity_type: &str) -> Vec<&SatRecord> {
self.records
.iter()
.filter(|r| r.entity_type == entity_type)
.collect()
}
pub fn bodies(&self) -> Vec<SatBody<'_>> {
self.records
.iter()
.filter_map(SatBody::from_record)
.collect()
}
pub fn faces(&self) -> Vec<SatFace<'_>> {
self.records
.iter()
.filter_map(SatFace::from_record)
.collect()
}
pub fn edges(&self) -> Vec<SatEdge<'_>> {
self.records
.iter()
.filter_map(SatEdge::from_record)
.collect()
}
pub fn vertices(&self) -> Vec<SatVertex<'_>> {
self.records
.iter()
.filter_map(SatVertex::from_record)
.collect()
}
pub fn add_record(&mut self, mut record: SatRecord) -> i32 {
let index = self.records.len() as i32;
record.index = index;
self.records.push(record);
self.header.num_records = self.records.len();
index
}
pub fn resolve(&self, ptr: SatPointer) -> Option<&SatRecord> {
if ptr.is_null() {
None
} else {
self.record(ptr.0 as usize)
}
}
pub fn validate(&self) -> Vec<SatValidationError> {
let mut errors = Vec::new();
let max_index = self.records.len() as i32;
for record in &self.records {
if !record.attribute.is_null() {
if let Some(idx) = record.attribute.index() {
if idx as i32 >= max_index {
errors.push(SatValidationError::InvalidPointer {
record_index: record.index,
pointer_value: record.attribute.0,
context: "attribute".to_string(),
});
}
}
}
for (i, token) in record.tokens.iter().enumerate() {
if let SatToken::Pointer(p) = token {
if !p.is_null() {
if let Some(idx) = p.index() {
if idx as i32 >= max_index {
errors.push(SatValidationError::InvalidPointer {
record_index: record.index,
pointer_value: p.0,
context: format!("token[{}]", i),
});
}
}
}
}
}
}
errors
}
pub fn strip_for_sab(&mut self) {
let keep: Vec<bool> = self
.records
.iter()
.map(|r| Self::is_core_geometry_type(&r.entity_type))
.collect();
let kept_count = keep.iter().filter(|&&k| k).count();
if kept_count == self.records.len() {
return; }
let mut index_map = vec![-1i32; self.records.len()];
let mut new_idx: i32 = 0;
for (old_idx, &kept) in keep.iter().enumerate() {
if kept {
index_map[old_idx] = new_idx;
new_idx += 1;
}
}
let remap = |p: i32| -> i32 {
if p < 0 || (p as usize) >= index_map.len() {
-1
} else {
index_map[p as usize]
}
};
let mut new_records = Vec::with_capacity(kept_count);
let old_records = std::mem::take(&mut self.records);
for (old_idx, record) in old_records.into_iter().enumerate() {
if !keep[old_idx] {
continue;
}
let mut rec = record;
rec.index = index_map[old_idx];
rec.attribute = SatPointer::new(remap(rec.attribute.0));
for token in &mut rec.tokens {
if let SatToken::Pointer(p) = token {
p.0 = remap(p.0);
}
}
new_records.push(rec);
}
self.records = new_records;
self.header.num_records = self.records.len();
self.header.spatial_resolution = 1.0;
}
fn is_core_geometry_type(entity_type: &str) -> bool {
let base = if let Some(pos) = entity_type.rfind('-') {
&entity_type[pos + 1..]
} else {
entity_type
};
matches!(
base,
"body"
| "lump"
| "shell"
| "subshell"
| "face"
| "loop"
| "coedge"
| "edge"
| "vertex"
| "wire"
| "point"
| "curve"
| "surface"
| "pcurve"
| "transform"
| "asmheader"
)
}
}
impl Default for SatDocument {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SatParseError {
EmptyInput,
InvalidHeader(String),
InvalidProductInfo(String),
InvalidTolerances(String),
InvalidRecord {
line: usize,
message: String,
},
UnexpectedToken {
line: usize,
token: String,
},
}
impl fmt::Display for SatParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyInput => write!(f, "SAT data is empty"),
Self::InvalidHeader(msg) => write!(f, "Invalid SAT header: {}", msg),
Self::InvalidProductInfo(msg) => write!(f, "Invalid product info: {}", msg),
Self::InvalidTolerances(msg) => write!(f, "Invalid tolerances: {}", msg),
Self::InvalidRecord { line, message } => {
write!(f, "Invalid SAT record at line {}: {}", line, message)
}
Self::UnexpectedToken { line, token } => {
write!(f, "Unexpected token '{}' at line {}", token, line)
}
}
}
}
impl std::error::Error for SatParseError {}
#[derive(Debug, Clone, PartialEq)]
pub enum SatValidationError {
InvalidPointer {
record_index: i32,
pointer_value: i32,
context: String,
},
}
impl fmt::Display for SatValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidPointer {
record_index,
pointer_value,
context,
} => write!(
f,
"Record {} has invalid pointer ${} in {}",
record_index, pointer_value, context
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SatEntityCategory {
Header,
Topology,
Geometry,
Transform,
Attribute,
Unknown,
}
pub fn classify_entity_type(entity_type: &str) -> SatEntityCategory {
match entity_type {
"asmheader" => SatEntityCategory::Header,
"body" | "lump" | "shell" | "subshell" | "face" | "loop" | "coedge" | "edge"
| "vertex" | "wire" => SatEntityCategory::Topology,
"point" | "straight-curve" | "ellipse-curve" | "intcurve-curve" | "bs3-curve"
| "plane-surface" | "cone-surface" | "sphere-surface" | "torus-surface"
| "spline-surface" | "meshsurf-surface" | "bs3-surface" => SatEntityCategory::Geometry,
"transform" => SatEntityCategory::Transform,
_ if entity_type.ends_with("-attrib") || entity_type.starts_with("attrib") => {
SatEntityCategory::Attribute
}
_ => SatEntityCategory::Unknown,
}
}