use crate::entities::{Entity, EntityCommon};
use crate::entities::attribute_definition::{
AttributeFlags, HorizontalAlignment, VerticalAlignment, MTextFlag
};
use crate::types::{BoundingBox3D, Color, Handle, LineWeight, Transparency, Vector3};
use std::f64::consts::PI;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AttributeEntity {
pub common: EntityCommon,
pub tag: String,
pub 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 attdef_handle: Handle,
pub lock_position: bool,
}
impl AttributeEntity {
pub fn new(tag: String, value: String) -> Self {
Self {
common: EntityCommon::default(),
tag,
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,
attdef_handle: Handle::NULL,
lock_position: false,
}
}
pub fn simple(tag: impl Into<String>, value: impl Into<String>) -> Self {
Self::new(tag.into(), value.into())
}
pub fn from_definition(
attdef: &crate::entities::AttributeDefinition,
value: Option<String>,
) -> Self {
Self {
common: EntityCommon {
layer: attdef.common.layer.clone(),
color: attdef.common.color,
..Default::default()
},
tag: attdef.tag.clone(),
value: value.unwrap_or_else(|| attdef.default_value.clone()),
insertion_point: attdef.insertion_point,
alignment_point: attdef.alignment_point,
height: attdef.height,
rotation: attdef.rotation,
width_factor: attdef.width_factor,
oblique_angle: attdef.oblique_angle,
text_style: attdef.text_style.clone(),
text_generation_flags: attdef.text_generation_flags,
horizontal_alignment: attdef.horizontal_alignment,
vertical_alignment: attdef.vertical_alignment,
flags: attdef.flags,
field_length: attdef.field_length,
normal: attdef.normal,
mtext_flag: attdef.mtext_flag,
is_multiline: attdef.is_multiline,
line_count: attdef.line_count,
attdef_handle: attdef.common.handle,
lock_position: attdef.lock_position,
}
}
pub fn set_value(&mut self, value: impl Into<String>) {
self.value = value.into();
}
pub fn get_value(&self) -> &str {
&self.value
}
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 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.value.len() as f64 * char_width
}
pub fn apply_insert_transform(
&mut self,
insert_point: Vector3,
scale: Vector3,
rotation: f64,
) {
let scaled = Vector3::new(
self.insertion_point.x * scale.x,
self.insertion_point.y * scale.y,
self.insertion_point.z * scale.z,
);
let cos_r = rotation.cos();
let sin_r = rotation.sin();
let rotated = Vector3::new(
scaled.x * cos_r - scaled.y * sin_r,
scaled.x * sin_r + scaled.y * cos_r,
scaled.z,
);
self.insertion_point = rotated + insert_point;
let scaled_align = Vector3::new(
self.alignment_point.x * scale.x,
self.alignment_point.y * scale.y,
self.alignment_point.z * scale.z,
);
let rotated_align = Vector3::new(
scaled_align.x * cos_r - scaled_align.y * sin_r,
scaled_align.x * sin_r + scaled_align.y * cos_r,
scaled_align.z,
);
self.alignment_point = rotated_align + insert_point;
self.rotation += rotation;
let text_scale = (scale.x.abs() + scale.y.abs()) / 2.0;
self.height *= text_scale;
}
pub fn with_value(mut self, value: impl Into<String>) -> Self {
self.value = value.into();
self
}
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_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 AttributeEntity {
fn default() -> Self {
Self::new("ATTRIBUTE".to_string(), String::new())
}
}
impl Entity for AttributeEntity {
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_entity(self, offset);
}
fn entity_type(&self) -> &'static str {
"ATTRIB"
}
fn apply_transform(&mut self, transform: &crate::types::Transform) {
super::transform::transform_attribute_entity(self, transform);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::entities::AttributeDefinition;
#[test]
fn test_attrib_creation() {
let attrib = AttributeEntity::new(
"TAG".to_string(),
"value".to_string(),
);
assert_eq!(attrib.tag, "TAG");
assert_eq!(attrib.value, "value");
}
#[test]
fn test_attrib_simple() {
let attrib = AttributeEntity::simple("PART_NO", "12345");
assert_eq!(attrib.tag, "PART_NO");
assert_eq!(attrib.value, "12345");
}
#[test]
fn test_attrib_from_definition() {
let attdef = AttributeDefinition::new(
"TAG".to_string(),
"Enter:".to_string(),
"default".to_string(),
);
let attrib = AttributeEntity::from_definition(&attdef, Some("custom".to_string()));
assert_eq!(attrib.tag, "TAG");
assert_eq!(attrib.value, "custom");
let attrib2 = AttributeEntity::from_definition(&attdef, None);
assert_eq!(attrib2.value, "default");
}
#[test]
fn test_attrib_set_value() {
let mut attrib = AttributeEntity::simple("TAG", "old");
attrib.set_value("new");
assert_eq!(attrib.get_value(), "new");
}
#[test]
fn test_attrib_rotation() {
let mut attrib = AttributeEntity::default();
attrib.set_rotation_degrees(90.0);
assert!((attrib.rotation_degrees() - 90.0).abs() < 1e-10);
}
#[test]
fn test_attrib_translate() {
let mut attrib = AttributeEntity::default();
attrib.insertion_point = Vector3::new(10.0, 20.0, 0.0);
attrib.alignment_point = Vector3::new(10.0, 20.0, 0.0);
attrib.translate(Vector3::new(5.0, 5.0, 0.0));
assert_eq!(attrib.insertion_point, Vector3::new(15.0, 25.0, 0.0));
assert_eq!(attrib.alignment_point, Vector3::new(15.0, 25.0, 0.0));
}
#[test]
fn test_attrib_insert_transform() {
let mut attrib = AttributeEntity::simple("TAG", "value");
attrib.insertion_point = Vector3::new(10.0, 0.0, 0.0);
attrib.alignment_point = Vector3::new(10.0, 0.0, 0.0);
attrib.height = 2.5;
attrib.apply_insert_transform(
Vector3::new(100.0, 100.0, 0.0),
Vector3::new(2.0, 2.0, 1.0),
0.0,
);
assert!((attrib.insertion_point.x - 120.0).abs() < 1e-10);
assert!((attrib.insertion_point.y - 100.0).abs() < 1e-10);
assert!((attrib.height - 5.0).abs() < 1e-10);
}
#[test]
fn test_attrib_insert_transform_with_rotation() {
let mut attrib = AttributeEntity::simple("TAG", "value");
attrib.insertion_point = Vector3::new(10.0, 0.0, 0.0);
attrib.alignment_point = Vector3::new(10.0, 0.0, 0.0);
let rotation = PI / 2.0;
attrib.apply_insert_transform(
Vector3::ZERO,
Vector3::new(1.0, 1.0, 1.0),
rotation,
);
assert!(attrib.insertion_point.x.abs() < 1e-10);
assert!((attrib.insertion_point.y - 10.0).abs() < 1e-10);
}
#[test]
fn test_attrib_visibility() {
let mut attrib = AttributeEntity::default();
assert!(attrib.is_visible());
attrib.flags.invisible = true;
assert!(!attrib.is_visible());
}
#[test]
fn test_attrib_builder() {
let attrib = AttributeEntity::simple("TAG", "")
.with_value("test_value")
.with_position(Vector3::new(10.0, 10.0, 0.0))
.with_height(5.0)
.with_rotation_degrees(45.0)
.with_layer("ATTRIBUTES");
assert_eq!(attrib.value, "test_value");
assert_eq!(attrib.insertion_point, Vector3::new(10.0, 10.0, 0.0));
assert_eq!(attrib.height, 5.0);
assert_eq!(attrib.common.layer, "ATTRIBUTES");
}
#[test]
fn test_attrib_estimated_width() {
let attrib = AttributeEntity::simple("TAG", "Hello");
let width = attrib.estimated_width();
assert!((width - 7.5).abs() < 1e-10);
}
}