acadrust 0.5.2

A pure Rust library for reading and writing CAD files in DXF format (ASCII and Binary) and DWG format (Binary).
Documentation
//! Persistent association metadata for centre-line entities.
//!
//! A centre line is rendered and exchanged as an ordinary line while this
//! compact XDATA payload retains the source geometry needed to regenerate it.

use crate::types::{Handle, Vector3};
use crate::xdata::{ExtendedData, ExtendedDataRecord, XDataValue};

/// Registered application name used by centre-line association records.
pub const CENTERLINE_XDATA_APPLICATION: &str = "OCS_CENTERLINE";
/// Registered application name used by centre-mark association records.
pub const CENTERMARK_XDATA_APPLICATION: &str = "OCS_CENTERMARK";
const SIGNATURE: &str = "CENTERLINE_ASSOCIATION";
const MARK_SIGNATURE: &str = "CENTERMARK_ASSOCIATION";
const VERSION: i16 = 1;

/// Kind of source geometry referenced by a centre line.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CenterLineSourceKind {
    Line,
    LwPolylineSegment,
    Polyline2DSegment,
}

impl CenterLineSourceKind {
    fn code(self) -> i16 {
        match self {
            Self::Line => 0,
            Self::LwPolylineSegment => 1,
            Self::Polyline2DSegment => 2,
        }
    }

    fn from_code(code: i16) -> Option<Self> {
        match code {
            0 => Some(Self::Line),
            1 => Some(Self::LwPolylineSegment),
            2 => Some(Self::Polyline2DSegment),
            _ => None,
        }
    }
}

/// One selected source line or linear polyline segment.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CenterLineSource {
    pub handle: Handle,
    pub kind: CenterLineSourceKind,
    pub segment_index: i32,
    pub pick_point: Vector3,
}

/// Complete, versioned metadata required to regenerate a centre line.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CenterLineAssociation {
    pub first: CenterLineSource,
    pub second: CenterLineSource,
    pub plane_origin: Vector3,
    pub plane_x: Vector3,
    pub plane_y: Vector3,
    pub start_extension: f64,
    pub end_extension: f64,
    pub start_length_adjustment: f64,
    pub end_length_adjustment: f64,
    pub associated: bool,
}

impl CenterLineAssociation {
    /// Decode association metadata, rejecting incomplete or future payloads.
    pub fn read(data: &ExtendedData) -> Option<Self> {
        let values = &data.get_record(CENTERLINE_XDATA_APPLICATION)?.values;
        let [
            XDataValue::String(signature),
            XDataValue::Integer16(version),
            XDataValue::Handle(first_handle),
            XDataValue::Integer16(first_kind),
            XDataValue::Integer32(first_segment),
            XDataValue::Point3D(first_pick),
            XDataValue::Handle(second_handle),
            XDataValue::Integer16(second_kind),
            XDataValue::Integer32(second_segment),
            XDataValue::Point3D(second_pick),
            XDataValue::Point3D(plane_origin),
            XDataValue::Direction3D(plane_x),
            XDataValue::Direction3D(plane_y),
            XDataValue::Distance(start_extension),
            XDataValue::Distance(end_extension),
            XDataValue::Distance(start_length_adjustment),
            XDataValue::Distance(end_length_adjustment),
            XDataValue::Integer16(flags),
        ] = values.as_slice()
        else {
            return None;
        };
        if signature != SIGNATURE || *version != VERSION {
            return None;
        }
        Some(Self {
            first: CenterLineSource {
                handle: *first_handle,
                kind: CenterLineSourceKind::from_code(*first_kind)?,
                segment_index: *first_segment,
                pick_point: *first_pick,
            },
            second: CenterLineSource {
                handle: *second_handle,
                kind: CenterLineSourceKind::from_code(*second_kind)?,
                segment_index: *second_segment,
                pick_point: *second_pick,
            },
            plane_origin: *plane_origin,
            plane_x: *plane_x,
            plane_y: *plane_y,
            start_extension: *start_extension,
            end_extension: *end_extension,
            start_length_adjustment: *start_length_adjustment,
            end_length_adjustment: *end_length_adjustment,
            associated: flags & 1 != 0,
        })
    }

    /// Replace the association payload without disturbing unrelated XDATA.
    pub fn write(&self, data: &mut ExtendedData) {
        let mut record = ExtendedDataRecord::new(CENTERLINE_XDATA_APPLICATION);
        record.values = vec![
            XDataValue::String(SIGNATURE.to_owned()),
            XDataValue::Integer16(VERSION),
            XDataValue::Handle(self.first.handle),
            XDataValue::Integer16(self.first.kind.code()),
            XDataValue::Integer32(self.first.segment_index),
            XDataValue::Point3D(self.first.pick_point),
            XDataValue::Handle(self.second.handle),
            XDataValue::Integer16(self.second.kind.code()),
            XDataValue::Integer32(self.second.segment_index),
            XDataValue::Point3D(self.second.pick_point),
            XDataValue::Point3D(self.plane_origin),
            XDataValue::Direction3D(self.plane_x),
            XDataValue::Direction3D(self.plane_y),
            XDataValue::Distance(self.start_extension),
            XDataValue::Distance(self.end_extension),
            XDataValue::Distance(self.start_length_adjustment),
            XDataValue::Distance(self.end_length_adjustment),
            XDataValue::Integer16(i16::from(self.associated)),
        ];
        data.upsert_record(record);
    }

    /// Remove only centre-line metadata, leaving other applications intact.
    pub fn remove(data: &mut ExtendedData) {
        data.remove_record(CENTERLINE_XDATA_APPLICATION);
    }
}

/// Kind of circular source referenced by a centre mark.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CenterMarkSourceKind {
    Circle,
    Arc,
    LwPolylineArcSegment,
    Polyline2DArcSegment,
}

impl CenterMarkSourceKind {
    fn code(self) -> i16 {
        match self {
            Self::Circle => 0,
            Self::Arc => 1,
            Self::LwPolylineArcSegment => 2,
            Self::Polyline2DArcSegment => 3,
        }
    }

    fn from_code(code: i16) -> Option<Self> {
        match code {
            0 => Some(Self::Circle),
            1 => Some(Self::Arc),
            2 => Some(Self::LwPolylineArcSegment),
            3 => Some(Self::Polyline2DArcSegment),
            _ => None,
        }
    }
}

/// One selected circle, arc, or circular polyline segment.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CenterMarkSource {
    pub handle: Handle,
    pub kind: CenterMarkSourceKind,
    pub segment_index: i32,
    pub pick_point: Vector3,
}

/// Complete, versioned metadata required to regenerate a smart centre mark.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CenterMarkAssociation {
    pub source: CenterMarkSource,
    pub plane_origin: Vector3,
    pub plane_x: Vector3,
    pub plane_y: Vector3,
    pub center: Vector3,
    pub radius: f64,
    pub cross_size: f64,
    pub cross_gap: f64,
    pub cross_size_relative: bool,
    pub cross_gap_relative: bool,
    pub extension_length: f64,
    pub length_adjustments: [f64; 4],
    pub overshoots: [f64; 4],
    pub show_extensions: bool,
    pub associated: bool,
}

impl CenterMarkAssociation {
    /// Decode association metadata, rejecting incomplete or future payloads.
    pub fn read(data: &ExtendedData) -> Option<Self> {
        let values = &data.get_record(CENTERMARK_XDATA_APPLICATION)?.values;
        let [
            XDataValue::String(signature),
            XDataValue::Integer16(version),
            XDataValue::Handle(source_handle),
            XDataValue::Integer16(source_kind),
            XDataValue::Integer32(segment_index),
            XDataValue::Point3D(pick_point),
            XDataValue::Point3D(plane_origin),
            XDataValue::Direction3D(plane_x),
            XDataValue::Direction3D(plane_y),
            XDataValue::Point3D(center),
            XDataValue::Distance(radius),
            XDataValue::Distance(cross_size),
            XDataValue::Distance(cross_gap),
            XDataValue::Distance(extension_length),
            XDataValue::Distance(length_0),
            XDataValue::Distance(length_1),
            XDataValue::Distance(length_2),
            XDataValue::Distance(length_3),
            XDataValue::Distance(overshoot_0),
            XDataValue::Distance(overshoot_1),
            XDataValue::Distance(overshoot_2),
            XDataValue::Distance(overshoot_3),
            XDataValue::Integer16(flags),
        ] = values.as_slice()
        else {
            return None;
        };
        if signature != MARK_SIGNATURE || *version != VERSION {
            return None;
        }
        Some(Self {
            source: CenterMarkSource {
                handle: *source_handle,
                kind: CenterMarkSourceKind::from_code(*source_kind)?,
                segment_index: *segment_index,
                pick_point: *pick_point,
            },
            plane_origin: *plane_origin,
            plane_x: *plane_x,
            plane_y: *plane_y,
            center: *center,
            radius: *radius,
            cross_size: *cross_size,
            cross_gap: *cross_gap,
            extension_length: *extension_length,
            length_adjustments: [*length_0, *length_1, *length_2, *length_3],
            overshoots: [*overshoot_0, *overshoot_1, *overshoot_2, *overshoot_3],
            show_extensions: flags & 1 != 0,
            associated: flags & 2 != 0,
            cross_size_relative: flags & 4 != 0,
            cross_gap_relative: flags & 8 != 0,
        })
    }

    /// Replace the association payload without disturbing unrelated XDATA.
    pub fn write(&self, data: &mut ExtendedData) {
        let mut record = ExtendedDataRecord::new(CENTERMARK_XDATA_APPLICATION);
        record.values = vec![
            XDataValue::String(MARK_SIGNATURE.to_owned()),
            XDataValue::Integer16(VERSION),
            XDataValue::Handle(self.source.handle),
            XDataValue::Integer16(self.source.kind.code()),
            XDataValue::Integer32(self.source.segment_index),
            XDataValue::Point3D(self.source.pick_point),
            XDataValue::Point3D(self.plane_origin),
            XDataValue::Direction3D(self.plane_x),
            XDataValue::Direction3D(self.plane_y),
            XDataValue::Point3D(self.center),
            XDataValue::Distance(self.radius),
            XDataValue::Distance(self.cross_size),
            XDataValue::Distance(self.cross_gap),
            XDataValue::Distance(self.extension_length),
            XDataValue::Distance(self.length_adjustments[0]),
            XDataValue::Distance(self.length_adjustments[1]),
            XDataValue::Distance(self.length_adjustments[2]),
            XDataValue::Distance(self.length_adjustments[3]),
            XDataValue::Distance(self.overshoots[0]),
            XDataValue::Distance(self.overshoots[1]),
            XDataValue::Distance(self.overshoots[2]),
            XDataValue::Distance(self.overshoots[3]),
            XDataValue::Integer16(
                i16::from(self.show_extensions)
                    | (i16::from(self.associated) << 1)
                    | (i16::from(self.cross_size_relative) << 2)
                    | (i16::from(self.cross_gap_relative) << 3),
            ),
        ];
        data.upsert_record(record);
    }

    /// Remove only centre-mark metadata, leaving other applications intact.
    pub fn remove(data: &mut ExtendedData) {
        data.remove_record(CENTERMARK_XDATA_APPLICATION);
    }
}