use crate::entities::{Entity, EntityCommon};
use crate::types::{BoundingBox3D, Color, Handle, LineWeight, Transparency, Vector3};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum WireType {
#[default]
Unknown = 0,
Silhouette = 1,
VisibleEdge = 2,
HiddenEdge = 3,
Isoline = 4,
}
impl From<u8> for WireType {
fn from(value: u8) -> Self {
match value {
1 => Self::Silhouette,
2 => Self::VisibleEdge,
3 => Self::HiddenEdge,
4 => Self::Isoline,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Wire {
pub acis_index: i32,
pub wire_type: WireType,
pub selection_marker: i32,
pub color: Color,
pub points: Vec<Vector3>,
pub has_transform: bool,
pub has_rotation: bool,
pub has_shear: bool,
pub has_reflection: bool,
pub scale: f64,
pub translation: Vector3,
pub x_axis: Vector3,
pub y_axis: Vector3,
pub z_axis: Vector3,
}
impl Wire {
pub fn new() -> Self {
Self {
acis_index: 0,
wire_type: WireType::Unknown,
selection_marker: 0,
color: Color::ByLayer,
points: Vec::new(),
has_transform: false,
has_rotation: false,
has_shear: false,
has_reflection: false,
scale: 1.0,
translation: Vector3::ZERO,
x_axis: Vector3::UNIT_X,
y_axis: Vector3::UNIT_Y,
z_axis: Vector3::UNIT_Z,
}
}
pub fn from_points(points: Vec<Vector3>) -> Self {
let mut wire = Self::new();
wire.points = points;
wire
}
pub fn add_point(&mut self, point: Vector3) {
self.points.push(point);
}
pub fn point_count(&self) -> usize {
self.points.len()
}
pub fn bounding_box(&self) -> Option<BoundingBox3D> {
if self.points.is_empty() {
return None;
}
let first = self.points[0];
let mut min = first;
let mut max = first;
for pt in &self.points[1..] {
min.x = min.x.min(pt.x);
min.y = min.y.min(pt.y);
min.z = min.z.min(pt.z);
max.x = max.x.max(pt.x);
max.y = max.y.max(pt.y);
max.z = max.z.max(pt.z);
}
Some(BoundingBox3D::new(min, max))
}
}
impl Default for Wire {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Silhouette {
pub viewport_id: i64,
pub view_direction: Vector3,
pub up_vector: Vector3,
pub target: Vector3,
pub is_perspective: bool,
pub wires: Vec<Wire>,
}
impl Silhouette {
pub fn new(viewport_id: i64) -> Self {
Self {
viewport_id,
view_direction: Vector3::new(0.0, 0.0, 1.0),
up_vector: Vector3::new(0.0, 1.0, 0.0),
target: Vector3::ZERO,
is_perspective: false,
wires: Vec::new(),
}
}
pub fn with_view(viewport_id: i64, view_direction: Vector3, up_vector: Vector3) -> Self {
Self {
viewport_id,
view_direction: view_direction.normalize(),
up_vector: up_vector.normalize(),
target: Vector3::ZERO,
is_perspective: false,
wires: Vec::new(),
}
}
pub fn add_wire(&mut self, wire: Wire) {
self.wires.push(wire);
}
pub fn wire_count(&self) -> usize {
self.wires.len()
}
}
impl Default for Silhouette {
fn default() -> Self {
Self::new(0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum AcisVersion {
#[default]
Version1 = 1,
Version2 = 2,
}
impl From<u8> for AcisVersion {
fn from(value: u8) -> Self {
match value {
1 => Self::Version1,
2 => Self::Version2,
_ => Self::Version1,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AcisData {
pub version: AcisVersion,
pub sat_data: String,
pub sab_data: Vec<u8>,
pub is_binary: bool,
}
impl AcisData {
pub fn new() -> Self {
Self {
version: AcisVersion::Version1,
sat_data: String::new(),
sab_data: Vec::new(),
is_binary: false,
}
}
pub fn from_sat(sat: &str) -> Self {
Self {
version: AcisVersion::Version1,
sat_data: Self::strip_sat_terminator(sat),
sab_data: Vec::new(),
is_binary: false,
}
}
pub fn from_sab(sab: Vec<u8>) -> Self {
Self {
version: AcisVersion::Version2,
sat_data: String::new(),
sab_data: sab,
is_binary: true,
}
}
pub fn has_data(&self) -> bool {
!self.sat_data.is_empty() || !self.sab_data.is_empty()
}
pub fn strip_sat_terminator(sat: &str) -> String {
let mut result = String::with_capacity(sat.len());
for line in sat.lines() {
if line.starts_with("End-of-ACIS-data")
|| line.starts_with("End-of-ASM-data")
{
break;
}
result.push_str(line);
result.push('\n');
}
result
}
pub fn size(&self) -> usize {
if self.is_binary {
self.sab_data.len()
} else {
self.sat_data.len()
}
}
pub fn parse_sat(&self) -> Option<crate::entities::acis::SatDocument> {
if self.is_binary || self.sat_data.is_empty() {
return None;
}
crate::entities::acis::SatDocument::parse(&self.sat_data).ok()
}
pub fn from_sat_document(doc: &crate::entities::acis::SatDocument) -> Self {
Self::from_sat(&doc.to_sat_string())
}
}
impl AcisData {
fn sat_cipher(text: &str) -> String {
text.chars()
.map(|c| {
let b = c as u32;
if b >= 0x21 && b <= 0x7E {
char::from_u32(159 - b).unwrap_or(c)
} else {
c
}
})
.collect()
}
pub fn encode_sat(text: &str) -> String {
let ciphered = Self::sat_cipher(text);
let bytes = ciphered.as_bytes();
let mut result = String::with_capacity(ciphered.len() + 16);
for i in 0..bytes.len() {
result.push(bytes[i] as char);
if bytes[i] == 0x5E {
if let Some(&next) = bytes.get(i + 1) {
if (0x40..=0x5F).contains(&next) {
result.push(' ');
}
}
}
}
result
}
pub fn decode_sat(text: &str) -> String {
let bytes = text.as_bytes();
let mut cleaned = String::with_capacity(text.len());
let mut i = 0;
while i < bytes.len() {
cleaned.push(bytes[i] as char);
if bytes[i] == 0x5E && i + 2 < bytes.len() && bytes[i + 1] == 0x20 {
let after = bytes[i + 2];
if (0x40..=0x5F).contains(&after) {
i += 1; }
}
i += 1;
}
Self::sat_cipher(&cleaned)
}
}
impl Default for AcisData {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Solid3D {
pub common: EntityCommon,
pub uid: String,
pub point_of_reference: Vector3,
pub acis_data: AcisData,
pub wires: Vec<Wire>,
pub silhouettes: Vec<Silhouette>,
pub history_handle: Option<Handle>,
}
impl Solid3D {
pub fn new() -> Self {
Self {
common: EntityCommon::default(),
uid: String::new(),
point_of_reference: Vector3::ZERO,
acis_data: AcisData::new(),
wires: Vec::new(),
silhouettes: Vec::new(),
history_handle: None,
}
}
pub fn from_sat(sat: &str) -> Self {
let mut solid = Self::new();
solid.acis_data = AcisData::from_sat(sat);
solid
}
pub fn from_sab(sab: Vec<u8>) -> Self {
let mut solid = Self::new();
solid.acis_data = AcisData::from_sab(sab);
solid
}
pub fn set_sat_data(&mut self, sat: &str) {
self.acis_data = AcisData::from_sat(sat);
}
pub fn set_sab_data(&mut self, sab: Vec<u8>) {
self.acis_data = AcisData::from_sab(sab);
}
pub fn has_acis_data(&self) -> bool {
self.acis_data.has_data()
}
pub fn parse_sat(&self) -> Option<crate::entities::acis::SatDocument> {
if self.acis_data.is_binary || self.acis_data.sat_data.is_empty() {
return None;
}
crate::entities::acis::SatDocument::parse(&self.acis_data.sat_data).ok()
}
pub fn set_sat_document(&mut self, doc: &crate::entities::acis::SatDocument) {
self.acis_data = AcisData::from_sat(&doc.to_sat_string());
}
pub fn acis_size(&self) -> usize {
self.acis_data.size()
}
pub fn add_wire(&mut self, wire: Wire) {
self.wires.push(wire);
}
pub fn add_wire_from_points(&mut self, points: Vec<Vector3>) -> &mut Wire {
self.wires.push(Wire::from_points(points));
self.wires.last_mut().unwrap()
}
pub fn wire_count(&self) -> usize {
self.wires.len()
}
pub fn add_silhouette(&mut self, silhouette: Silhouette) {
self.silhouettes.push(silhouette);
}
pub fn silhouette_for_viewport(&self, viewport_id: i64) -> Option<&Silhouette> {
self.silhouettes.iter().find(|s| s.viewport_id == viewport_id)
}
pub fn clear_visualization(&mut self) {
self.wires.clear();
self.silhouettes.clear();
}
fn wireframe_bounding_box(&self) -> BoundingBox3D {
if self.wires.is_empty() {
return BoundingBox3D::default();
}
let mut min = Vector3::new(f64::MAX, f64::MAX, f64::MAX);
let mut max = Vector3::new(f64::MIN, f64::MIN, f64::MIN);
let mut has_points = false;
for wire in &self.wires {
for pt in &wire.points {
has_points = true;
min.x = min.x.min(pt.x);
min.y = min.y.min(pt.y);
min.z = min.z.min(pt.z);
max.x = max.x.max(pt.x);
max.y = max.y.max(pt.y);
max.z = max.z.max(pt.z);
}
}
if has_points {
BoundingBox3D::new(min, max)
} else {
BoundingBox3D::default()
}
}
}
impl Default for Solid3D {
fn default() -> Self {
Self::new()
}
}
impl Entity for Solid3D {
fn handle(&self) -> Handle {
self.common.handle
}
fn set_handle(&mut self, handle: Handle) {
self.common.handle = handle;
}
fn layer(&self) -> &str {
&self.common.layer
}
fn set_layer(&mut self, layer: String) {
self.common.layer = layer;
}
fn color(&self) -> Color {
self.common.color
}
fn set_color(&mut self, color: Color) {
self.common.color = color;
}
fn line_weight(&self) -> LineWeight {
self.common.line_weight
}
fn set_line_weight(&mut self, line_weight: LineWeight) {
self.common.line_weight = line_weight;
}
fn transparency(&self) -> Transparency {
self.common.transparency
}
fn set_transparency(&mut self, transparency: Transparency) {
self.common.transparency = transparency;
}
fn is_invisible(&self) -> bool {
self.common.invisible
}
fn set_invisible(&mut self, invisible: bool) {
self.common.invisible = invisible;
}
fn bounding_box(&self) -> BoundingBox3D {
self.wireframe_bounding_box()
}
fn translate(&mut self, offset: Vector3) {
super::translate::translate_solid3d(self, offset);
}
fn entity_type(&self) -> &'static str {
"3DSOLID"
}
fn apply_transform(&mut self, transform: &crate::types::Transform) {
super::transform::transform_solid3d(self, transform);
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Region {
pub common: EntityCommon,
pub uid: String,
pub point_of_reference: Vector3,
pub acis_data: AcisData,
pub wires: Vec<Wire>,
pub silhouettes: Vec<Silhouette>,
}
impl Region {
pub fn new() -> Self {
Self {
common: EntityCommon::default(),
uid: String::new(),
point_of_reference: Vector3::ZERO,
acis_data: AcisData::new(),
wires: Vec::new(),
silhouettes: Vec::new(),
}
}
pub fn from_sat(sat: &str) -> Self {
let mut region = Self::new();
region.acis_data = AcisData::from_sat(sat);
region
}
pub fn has_acis_data(&self) -> bool {
self.acis_data.has_data()
}
pub fn parse_sat(&self) -> Option<crate::entities::acis::SatDocument> {
if self.acis_data.is_binary || self.acis_data.sat_data.is_empty() {
return None;
}
crate::entities::acis::SatDocument::parse(&self.acis_data.sat_data).ok()
}
pub fn set_sat_document(&mut self, doc: &crate::entities::acis::SatDocument) {
self.acis_data = AcisData::from_sat(&doc.to_sat_string());
}
pub fn add_wire(&mut self, wire: Wire) {
self.wires.push(wire);
}
}
impl Default for Region {
fn default() -> Self {
Self::new()
}
}
impl Entity for Region {
fn handle(&self) -> Handle {
self.common.handle
}
fn set_handle(&mut self, handle: Handle) {
self.common.handle = handle;
}
fn layer(&self) -> &str {
&self.common.layer
}
fn set_layer(&mut self, layer: String) {
self.common.layer = layer;
}
fn color(&self) -> Color {
self.common.color
}
fn set_color(&mut self, color: Color) {
self.common.color = color;
}
fn line_weight(&self) -> LineWeight {
self.common.line_weight
}
fn set_line_weight(&mut self, line_weight: LineWeight) {
self.common.line_weight = line_weight;
}
fn transparency(&self) -> Transparency {
self.common.transparency
}
fn set_transparency(&mut self, transparency: Transparency) {
self.common.transparency = transparency;
}
fn is_invisible(&self) -> bool {
self.common.invisible
}
fn set_invisible(&mut self, invisible: bool) {
self.common.invisible = invisible;
}
fn bounding_box(&self) -> BoundingBox3D {
if self.wires.is_empty() {
return BoundingBox3D::default();
}
let mut min = Vector3::new(f64::MAX, f64::MAX, f64::MAX);
let mut max = Vector3::new(f64::MIN, f64::MIN, f64::MIN);
for wire in &self.wires {
for pt in &wire.points {
min.x = min.x.min(pt.x);
min.y = min.y.min(pt.y);
min.z = min.z.min(pt.z);
max.x = max.x.max(pt.x);
max.y = max.y.max(pt.y);
max.z = max.z.max(pt.z);
}
}
BoundingBox3D::new(min, max)
}
fn translate(&mut self, offset: Vector3) {
super::translate::translate_region(self, offset);
}
fn entity_type(&self) -> &'static str {
"REGION"
}
fn apply_transform(&mut self, transform: &crate::types::Transform) {
super::transform::transform_region(self, transform);
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Body {
pub common: EntityCommon,
pub uid: String,
pub point_of_reference: Vector3,
pub acis_data: AcisData,
pub wires: Vec<Wire>,
pub silhouettes: Vec<Silhouette>,
}
impl Body {
pub fn new() -> Self {
Self {
common: EntityCommon::default(),
uid: String::new(),
point_of_reference: Vector3::ZERO,
acis_data: AcisData::new(),
wires: Vec::new(),
silhouettes: Vec::new(),
}
}
pub fn from_sat(sat: &str) -> Self {
let mut body = Self::new();
body.acis_data = AcisData::from_sat(sat);
body
}
pub fn has_acis_data(&self) -> bool {
self.acis_data.has_data()
}
pub fn parse_sat(&self) -> Option<crate::entities::acis::SatDocument> {
if self.acis_data.is_binary || self.acis_data.sat_data.is_empty() {
return None;
}
crate::entities::acis::SatDocument::parse(&self.acis_data.sat_data).ok()
}
pub fn set_sat_document(&mut self, doc: &crate::entities::acis::SatDocument) {
self.acis_data = AcisData::from_sat(&doc.to_sat_string());
}
pub fn add_wire(&mut self, wire: Wire) {
self.wires.push(wire);
}
}
impl Default for Body {
fn default() -> Self {
Self::new()
}
}
impl Entity for Body {
fn handle(&self) -> Handle {
self.common.handle
}
fn set_handle(&mut self, handle: Handle) {
self.common.handle = handle;
}
fn layer(&self) -> &str {
&self.common.layer
}
fn set_layer(&mut self, layer: String) {
self.common.layer = layer;
}
fn color(&self) -> Color {
self.common.color
}
fn set_color(&mut self, color: Color) {
self.common.color = color;
}
fn line_weight(&self) -> LineWeight {
self.common.line_weight
}
fn set_line_weight(&mut self, line_weight: LineWeight) {
self.common.line_weight = line_weight;
}
fn transparency(&self) -> Transparency {
self.common.transparency
}
fn set_transparency(&mut self, transparency: Transparency) {
self.common.transparency = transparency;
}
fn is_invisible(&self) -> bool {
self.common.invisible
}
fn set_invisible(&mut self, invisible: bool) {
self.common.invisible = invisible;
}
fn bounding_box(&self) -> BoundingBox3D {
if self.wires.is_empty() {
return BoundingBox3D::default();
}
let mut min = Vector3::new(f64::MAX, f64::MAX, f64::MAX);
let mut max = Vector3::new(f64::MIN, f64::MIN, f64::MIN);
for wire in &self.wires {
for pt in &wire.points {
min.x = min.x.min(pt.x);
min.y = min.y.min(pt.y);
min.z = min.z.min(pt.z);
max.x = max.x.max(pt.x);
max.y = max.y.max(pt.y);
max.z = max.z.max(pt.z);
}
}
BoundingBox3D::new(min, max)
}
fn translate(&mut self, offset: Vector3) {
super::translate::translate_body(self, offset);
}
fn entity_type(&self) -> &'static str {
"BODY"
}
fn apply_transform(&mut self, transform: &crate::types::Transform) {
super::transform::transform_body(self, transform);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_solid3d_creation() {
let solid = Solid3D::new();
assert!(solid.uid.is_empty());
assert_eq!(solid.point_of_reference, Vector3::ZERO);
assert!(!solid.has_acis_data());
}
#[test]
fn test_solid3d_from_sat() {
let sat = "400 0 1 0\n16 ASM-BODY 1.0 0\n";
let solid = Solid3D::from_sat(sat);
assert!(solid.has_acis_data());
assert!(!solid.acis_data.is_binary);
assert_eq!(solid.acis_data.sat_data, sat);
}
#[test]
fn test_solid3d_from_sab() {
let sab = b"ACIS BinaryFile".to_vec();
let solid = Solid3D::from_sab(sab.clone());
assert!(solid.has_acis_data());
assert!(solid.acis_data.is_binary);
assert_eq!(solid.acis_data.sab_data, sab);
}
#[test]
fn test_wire_creation() {
let wire = Wire::new();
assert_eq!(wire.wire_type, WireType::Unknown);
assert!(wire.points.is_empty());
assert!(!wire.has_transform);
}
#[test]
fn test_wire_from_points() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(1.0, 1.0, 0.0),
];
let wire = Wire::from_points(points.clone());
assert_eq!(wire.point_count(), 3);
assert_eq!(wire.points, points);
}
#[test]
fn test_wire_bounding_box() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 5.0, 3.0),
Vector3::new(5.0, 10.0, 1.0),
];
let wire = Wire::from_points(points);
let bbox = wire.bounding_box().unwrap();
assert_eq!(bbox.min, Vector3::new(0.0, 0.0, 0.0));
assert_eq!(bbox.max, Vector3::new(10.0, 10.0, 3.0));
}
#[test]
fn test_silhouette_creation() {
let silhouette = Silhouette::new(42);
assert_eq!(silhouette.viewport_id, 42);
assert!(!silhouette.is_perspective);
assert_eq!(silhouette.wire_count(), 0);
}
#[test]
fn test_silhouette_with_view() {
let silhouette = Silhouette::with_view(
1,
Vector3::new(0.0, 0.0, -1.0),
Vector3::new(0.0, 1.0, 0.0),
);
assert_eq!(silhouette.viewport_id, 1);
assert_eq!(silhouette.view_direction.z, -1.0);
}
#[test]
fn test_solid3d_add_wire() {
let mut solid = Solid3D::new();
solid.add_wire(Wire::from_points(vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 1.0, 1.0),
]));
assert_eq!(solid.wire_count(), 1);
}
#[test]
fn test_solid3d_translate() {
let mut solid = Solid3D::new();
solid.point_of_reference = Vector3::new(1.0, 2.0, 3.0);
solid.add_wire(Wire::from_points(vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
]));
solid.translate(Vector3::new(10.0, 20.0, 30.0));
assert_eq!(solid.point_of_reference, Vector3::new(11.0, 22.0, 33.0));
assert_eq!(solid.wires[0].points[0], Vector3::new(10.0, 20.0, 30.0));
assert_eq!(solid.wires[0].points[1], Vector3::new(11.0, 20.0, 30.0));
}
#[test]
fn test_acis_data() {
let mut data = AcisData::new();
assert!(!data.has_data());
assert_eq!(data.size(), 0);
data.sat_data = "test".to_string();
assert!(data.has_data());
assert_eq!(data.size(), 4);
}
#[test]
fn test_acis_version() {
assert_eq!(AcisVersion::from(1), AcisVersion::Version1);
assert_eq!(AcisVersion::from(2), AcisVersion::Version2);
assert_eq!(AcisVersion::from(99), AcisVersion::Version1);
}
#[test]
fn test_wire_type() {
assert_eq!(WireType::from(1), WireType::Silhouette);
assert_eq!(WireType::from(2), WireType::VisibleEdge);
assert_eq!(WireType::from(3), WireType::HiddenEdge);
assert_eq!(WireType::from(4), WireType::Isoline);
assert_eq!(WireType::from(99), WireType::Unknown);
}
#[test]
fn test_region_creation() {
let region = Region::new();
assert!(!region.has_acis_data());
assert_eq!(region.entity_type(), "REGION");
}
#[test]
fn test_body_creation() {
let body = Body::new();
assert!(!body.has_acis_data());
assert_eq!(body.entity_type(), "BODY");
}
#[test]
fn test_solid3d_silhouette_for_viewport() {
let mut solid = Solid3D::new();
solid.add_silhouette(Silhouette::new(1));
solid.add_silhouette(Silhouette::new(2));
assert!(solid.silhouette_for_viewport(1).is_some());
assert!(solid.silhouette_for_viewport(2).is_some());
assert!(solid.silhouette_for_viewport(3).is_none());
}
#[test]
fn test_solid3d_clear_visualization() {
let mut solid = Solid3D::new();
solid.add_wire(Wire::new());
solid.add_silhouette(Silhouette::new(1));
solid.clear_visualization();
assert_eq!(solid.wire_count(), 0);
assert!(solid.silhouettes.is_empty());
}
#[test]
fn test_solid3d_parse_sat() {
let sat_text = "700 0 1 0\n\
@8 acadrust @8 ACIS 7.0 @24 Thu Jan 01 00:00:00 2023\n\
1e-06 9.9999999999999995e-07\n\
asmheader $-1 -1 @12 700 7 0 0 @5 ACIS @3 7.0 @24 Thu Jan 01 00:00:00 2023 #\n\
body $-1 -1 $-1 $2 $-1 $-1 #\n\
lump $-1 -1 $-1 $-1 $3 $1 #\n\
shell $-1 -1 $-1 $-1 $-1 $4 $-1 $2 #\n\
face $-1 -1 $-1 $-1 $5 $3 $-1 $6 forward single #\n\
loop $-1 -1 $-1 $-1 $-1 $4 #\n\
plane-surface $-1 -1 $-1 0 0 5 0 0 1 1 0 0 forward_v I I I I #\n\
End-of-ACIS-data\n";
let solid = Solid3D::from_sat(sat_text);
assert!(solid.has_acis_data());
let doc = solid.parse_sat().unwrap();
assert_eq!(doc.header.version, crate::entities::acis::SatVersion::V7_0);
assert_eq!(doc.records.len(), 7);
let bodies = doc.bodies();
assert_eq!(bodies.len(), 1);
assert_eq!(bodies[0].lump(), crate::entities::acis::SatPointer::new(2));
let faces = doc.faces();
assert_eq!(faces.len(), 1);
assert_eq!(
faces[0].sense(),
crate::entities::acis::Sense::Forward
);
let planes = doc.records_of_type("plane-surface");
assert_eq!(planes.len(), 1);
let plane = crate::entities::acis::SatPlaneSurface::from_record(planes[0]).unwrap();
assert_eq!(plane.root_point(), (0.0, 0.0, 5.0));
assert_eq!(plane.normal(), (0.0, 0.0, 1.0));
}
#[test]
fn test_solid3d_set_sat_document() {
let mut doc = crate::entities::acis::SatDocument::new_body();
doc.add_plane_surface(
[0.0, 0.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 0.0, 0.0],
);
let mut solid = Solid3D::new();
solid.set_sat_document(&doc);
assert!(solid.has_acis_data());
assert!(!solid.acis_data.is_binary);
assert!(solid.acis_data.sat_data.contains("plane-surface"));
assert!(solid.acis_data.sat_data.contains("700"));
let doc2 = solid.parse_sat().unwrap();
assert_eq!(doc2.records.len(), doc.records.len());
}
#[test]
fn test_region_parse_sat() {
let sat_text = "700 0 1 0\n\
@8 acadrust @8 ACIS 7.0 @24 Thu Jan 01 00:00:00 2023\n\
1e-06 9.9999999999999995e-07\n\
-0 asmheader $-1 -1 @12 700 7 0 0 @5 ACIS @3 7.0 @24 Thu Jan 01 00:00:00 2023 #\n\
-1 body $-1 $-1 $-1 $-1 #\n\
End-of-ACIS-data\n";
let region = Region::from_sat(sat_text);
let doc = region.parse_sat().unwrap();
assert_eq!(doc.records.len(), 2);
}
#[test]
fn test_body_parse_sat() {
let sat_text = "700 0 1 0\n\
@8 acadrust @8 ACIS 7.0 @24 Thu Jan 01 00:00:00 2023\n\
1e-06 9.9999999999999995e-07\n\
-0 asmheader $-1 -1 @12 700 7 0 0 @5 ACIS @3 7.0 @24 Thu Jan 01 00:00:00 2023 #\n\
-1 body $-1 $-1 $-1 $-1 #\n\
End-of-ACIS-data\n";
let body = Body::from_sat(sat_text);
let doc = body.parse_sat().unwrap();
assert_eq!(doc.records.len(), 2);
}
#[test]
fn test_acis_data_from_sat_document() {
let doc = crate::entities::acis::SatDocument::new_body();
let acis = AcisData::from_sat_document(&doc);
assert!(acis.has_data());
assert!(!acis.is_binary);
assert!(acis.sat_data.contains("body"));
}
#[test]
fn test_solid3d_parse_sat_binary_returns_none() {
let solid = Solid3D::from_sab(b"ACIS BinaryFile".to_vec());
assert!(solid.parse_sat().is_none());
}
#[test]
fn test_solid3d_parse_sat_empty_returns_none() {
let solid = Solid3D::new();
assert!(solid.parse_sat().is_none());
}
}