use crate::entities::{Entity, EntityCommon};
use crate::types::{BoundingBox3D, Color, Handle, LineWeight, Transparency, Vector3};
use std::f64::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AttributeFlags {
pub invisible: bool,
pub constant: bool,
pub verify: bool,
pub preset: bool,
pub locked_position: bool,
pub annotative: bool,
}
impl AttributeFlags {
pub fn from_bits(bits: i32) -> Self {
Self {
invisible: (bits & 1) != 0,
constant: (bits & 2) != 0,
verify: (bits & 4) != 0,
preset: (bits & 8) != 0,
locked_position: (bits & 16) != 0,
annotative: (bits & 128) != 0,
}
}
pub fn to_bits(&self) -> i32 {
let mut bits = 0;
if self.invisible { bits |= 1; }
if self.constant { bits |= 2; }
if self.verify { bits |= 4; }
if self.preset { bits |= 8; }
if self.locked_position { bits |= 16; }
if self.annotative { bits |= 128; }
bits
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HorizontalAlignment {
#[default]
Left = 0,
Center = 1,
Right = 2,
Aligned = 3,
Middle = 4,
Fit = 5,
}
impl HorizontalAlignment {
pub fn from_value(value: i16) -> Self {
match value {
1 => HorizontalAlignment::Center,
2 => HorizontalAlignment::Right,
3 => HorizontalAlignment::Aligned,
4 => HorizontalAlignment::Middle,
5 => HorizontalAlignment::Fit,
_ => HorizontalAlignment::Left,
}
}
pub fn to_value(&self) -> i16 {
*self as i16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum VerticalAlignment {
#[default]
Baseline = 0,
Bottom = 1,
Middle = 2,
Top = 3,
}
impl VerticalAlignment {
pub fn from_value(value: i16) -> Self {
match value {
1 => VerticalAlignment::Bottom,
2 => VerticalAlignment::Middle,
3 => VerticalAlignment::Top,
_ => VerticalAlignment::Baseline,
}
}
pub fn to_value(&self) -> i16 {
*self as i16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MTextFlag {
#[default]
SingleLine = 0,
MultiLine = 2,
ConstantMultiLine = 4,
}
impl MTextFlag {
pub fn from_value(value: i16) -> Self {
match value {
2 => MTextFlag::MultiLine,
4 => MTextFlag::ConstantMultiLine,
_ => MTextFlag::SingleLine,
}
}
pub fn to_value(&self) -> i16 {
*self as i16
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AttributeDefinition {
pub common: EntityCommon,
pub tag: String,
pub prompt: String,
pub default_value: String,
pub insertion_point: Vector3,
pub alignment_point: Vector3,
pub height: f64,
pub rotation: f64,
pub width_factor: f64,
pub oblique_angle: f64,
pub text_style: String,
pub text_generation_flags: i16,
pub horizontal_alignment: HorizontalAlignment,
pub vertical_alignment: VerticalAlignment,
pub flags: AttributeFlags,
pub field_length: i16,
pub normal: Vector3,
pub mtext_flag: MTextFlag,
pub is_multiline: bool,
pub line_count: i16,
pub lock_position: bool,
}
impl AttributeDefinition {
pub fn new(tag: String, prompt: String, default_value: String) -> Self {
Self {
common: EntityCommon::default(),
tag,
prompt,
default_value,
insertion_point: Vector3::ZERO,
alignment_point: Vector3::ZERO,
height: 2.5,
rotation: 0.0,
width_factor: 1.0,
oblique_angle: 0.0,
text_style: "STANDARD".to_string(),
text_generation_flags: 0,
horizontal_alignment: HorizontalAlignment::Left,
vertical_alignment: VerticalAlignment::Baseline,
flags: AttributeFlags::default(),
field_length: 0,
normal: Vector3::UNIT_Z,
mtext_flag: MTextFlag::SingleLine,
is_multiline: false,
line_count: 1,
lock_position: false,
}
}
pub fn simple(tag: impl Into<String>) -> Self {
let tag = tag.into();
Self::new(tag.clone(), format!("Enter {}:", tag), String::new())
}
pub fn invisible(tag: impl Into<String>, default_value: impl Into<String>) -> Self {
let mut attdef = Self::simple(tag);
attdef.default_value = default_value.into();
attdef.flags.invisible = true;
attdef
}
pub fn constant(tag: impl Into<String>, value: impl Into<String>) -> Self {
let mut attdef = Self::simple(tag);
attdef.default_value = value.into();
attdef.flags.constant = true;
attdef
}
pub fn set_position(&mut self, point: Vector3) {
self.insertion_point = point;
self.alignment_point = point;
}
pub fn set_height(&mut self, height: f64) {
self.height = height;
}
pub fn set_rotation_degrees(&mut self, degrees: f64) {
self.rotation = degrees * PI / 180.0;
}
pub fn rotation_degrees(&self) -> f64 {
self.rotation * 180.0 / PI
}
pub fn set_text_style(&mut self, style: impl Into<String>) {
self.text_style = style.into();
}
pub fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
self.horizontal_alignment = horizontal;
self.vertical_alignment = vertical;
}
pub fn center_horizontal(&mut self) {
self.horizontal_alignment = HorizontalAlignment::Center;
}
pub fn center(&mut self) {
self.horizontal_alignment = HorizontalAlignment::Center;
self.vertical_alignment = VerticalAlignment::Middle;
}
pub fn set_invisible(&mut self, invisible: bool) {
self.flags.invisible = invisible;
}
pub fn set_constant(&mut self, constant: bool) {
self.flags.constant = constant;
}
pub fn set_preset(&mut self, preset: bool) {
self.flags.preset = preset;
}
pub fn set_verify(&mut self, verify: bool) {
self.flags.verify = verify;
}
pub fn is_visible(&self) -> bool {
!self.flags.invisible
}
pub fn is_constant(&self) -> bool {
self.flags.constant
}
pub fn estimated_width(&self) -> f64 {
let char_width = self.height * 0.6 * self.width_factor;
self.default_value.len() as f64 * char_width
}
pub fn with_position(mut self, point: Vector3) -> Self {
self.set_position(point);
self
}
pub fn with_height(mut self, height: f64) -> Self {
self.height = height;
self
}
pub fn with_rotation_degrees(mut self, degrees: f64) -> Self {
self.set_rotation_degrees(degrees);
self
}
pub fn with_text_style(mut self, style: impl Into<String>) -> Self {
self.text_style = style.into();
self
}
pub fn with_invisible(mut self) -> Self {
self.flags.invisible = true;
self
}
pub fn with_constant(mut self) -> Self {
self.flags.constant = true;
self
}
pub fn with_preset(mut self) -> Self {
self.flags.preset = true;
self
}
pub fn with_layer(mut self, layer: impl Into<String>) -> Self {
self.common.layer = layer.into();
self
}
pub fn with_color(mut self, color: Color) -> Self {
self.common.color = color;
self
}
}
impl Default for AttributeDefinition {
fn default() -> Self {
Self::simple("ATTRIBUTE")
}
}
impl Entity for AttributeDefinition {
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, weight: LineWeight) {
self.common.line_weight = 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 || self.flags.invisible
}
fn set_invisible(&mut self, invisible: bool) {
self.common.invisible = invisible;
}
fn bounding_box(&self) -> BoundingBox3D {
let width = self.estimated_width();
let height = self.height;
BoundingBox3D::new(
self.insertion_point,
Vector3::new(
self.insertion_point.x + width,
self.insertion_point.y + height,
self.insertion_point.z,
),
)
}
fn translate(&mut self, offset: Vector3) {
super::translate::translate_attribute_definition(self, offset);
}
fn entity_type(&self) -> &'static str {
"ATTDEF"
}
fn apply_transform(&mut self, transform: &crate::types::Transform) {
super::transform::transform_attribute_definition(self, transform);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attdef_creation() {
let attdef = AttributeDefinition::new(
"TAG".to_string(),
"Enter value:".to_string(),
"default".to_string(),
);
assert_eq!(attdef.tag, "TAG");
assert_eq!(attdef.prompt, "Enter value:");
assert_eq!(attdef.default_value, "default");
}
#[test]
fn test_attdef_simple() {
let attdef = AttributeDefinition::simple("PART_NO");
assert_eq!(attdef.tag, "PART_NO");
assert_eq!(attdef.prompt, "Enter PART_NO:");
}
#[test]
fn test_attdef_invisible() {
let attdef = AttributeDefinition::invisible("HIDDEN", "secret");
assert!(attdef.flags.invisible);
assert_eq!(attdef.default_value, "secret");
}
#[test]
fn test_attdef_constant() {
let attdef = AttributeDefinition::constant("VERSION", "1.0");
assert!(attdef.flags.constant);
assert_eq!(attdef.default_value, "1.0");
}
#[test]
fn test_attdef_rotation() {
let mut attdef = AttributeDefinition::default();
attdef.set_rotation_degrees(45.0);
assert!((attdef.rotation_degrees() - 45.0).abs() < 1e-10);
}
#[test]
fn test_attdef_flags() {
let flags = AttributeFlags::from_bits(7); assert!(flags.invisible);
assert!(flags.constant);
assert!(flags.verify);
assert!(!flags.preset);
assert_eq!(flags.to_bits(), 7);
}
#[test]
fn test_attdef_alignment() {
let mut attdef = AttributeDefinition::default();
attdef.set_alignment(HorizontalAlignment::Center, VerticalAlignment::Middle);
assert_eq!(attdef.horizontal_alignment, HorizontalAlignment::Center);
assert_eq!(attdef.vertical_alignment, VerticalAlignment::Middle);
}
#[test]
fn test_attdef_translate() {
let mut attdef = AttributeDefinition::default();
attdef.insertion_point = Vector3::new(10.0, 20.0, 0.0);
attdef.alignment_point = Vector3::new(10.0, 20.0, 0.0);
attdef.translate(Vector3::new(5.0, 5.0, 0.0));
assert_eq!(attdef.insertion_point, Vector3::new(15.0, 25.0, 0.0));
assert_eq!(attdef.alignment_point, Vector3::new(15.0, 25.0, 0.0));
}
#[test]
fn test_attdef_builder() {
let attdef = AttributeDefinition::simple("TEST")
.with_position(Vector3::new(10.0, 10.0, 0.0))
.with_height(5.0)
.with_rotation_degrees(90.0)
.with_invisible()
.with_layer("ATTRIBUTES");
assert_eq!(attdef.insertion_point, Vector3::new(10.0, 10.0, 0.0));
assert_eq!(attdef.height, 5.0);
assert!(attdef.flags.invisible);
assert_eq!(attdef.common.layer, "ATTRIBUTES");
}
#[test]
fn test_horizontal_alignment() {
assert_eq!(HorizontalAlignment::from_value(0), HorizontalAlignment::Left);
assert_eq!(HorizontalAlignment::from_value(1), HorizontalAlignment::Center);
assert_eq!(HorizontalAlignment::from_value(2), HorizontalAlignment::Right);
assert_eq!(HorizontalAlignment::Center.to_value(), 1);
}
#[test]
fn test_vertical_alignment() {
assert_eq!(VerticalAlignment::from_value(0), VerticalAlignment::Baseline);
assert_eq!(VerticalAlignment::from_value(2), VerticalAlignment::Middle);
assert_eq!(VerticalAlignment::Middle.to_value(), 2);
}
}