use crate::entities::{Entity, EntityCommon};
use crate::types::{BoundingBox3D, Color, Handle, Vector3};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LeaderPathType {
#[default]
StraightLine = 0,
Spline = 1,
}
impl LeaderPathType {
pub fn from_value(value: i16) -> Self {
match value {
1 => LeaderPathType::Spline,
_ => LeaderPathType::StraightLine,
}
}
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 LeaderCreationType {
#[default]
WithText = 0,
WithTolerance = 1,
WithBlock = 2,
NoAnnotation = 3,
}
impl LeaderCreationType {
pub fn from_value(value: i16) -> Self {
match value {
1 => LeaderCreationType::WithTolerance,
2 => LeaderCreationType::WithBlock,
3 => LeaderCreationType::NoAnnotation,
_ => LeaderCreationType::WithText,
}
}
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 HooklineDirection {
#[default]
Opposite = 0,
Same = 1,
}
impl HooklineDirection {
pub fn from_value(value: i16) -> Self {
match value {
1 => HooklineDirection::Same,
_ => HooklineDirection::Opposite,
}
}
pub fn to_value(&self) -> i16 {
*self as i16
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Leader {
pub common: EntityCommon,
pub dimension_style: String,
pub arrow_enabled: bool,
pub path_type: LeaderPathType,
pub creation_type: LeaderCreationType,
pub hookline_direction: HooklineDirection,
pub hookline_enabled: bool,
pub text_height: f64,
pub text_width: f64,
pub vertices: Vec<Vector3>,
pub override_color: Color,
pub annotation_handle: Handle,
pub normal: Vector3,
pub horizontal_direction: Vector3,
pub block_offset: Vector3,
pub annotation_offset: Vector3,
pub origin: Vector3,
pub dimension_gap: f64,
pub arrowhead_type: i16,
pub arrow_size: f64,
pub byblock_color: i16,
pub dwg_unknown_bit1: bool,
pub dwg_unknown_bit2: bool,
pub dwg_unknown_bit3: bool,
pub dwg_unknown_bit4: bool,
pub dwg_unknown_bit5: bool,
pub dwg_unknown_short1: i16,
}
impl Leader {
pub fn new() -> Self {
Self {
common: EntityCommon::default(),
dimension_style: "Standard".to_string(),
arrow_enabled: true,
path_type: LeaderPathType::StraightLine,
creation_type: LeaderCreationType::WithText,
hookline_direction: HooklineDirection::Opposite,
hookline_enabled: false,
text_height: 2.5,
text_width: 0.0,
vertices: Vec::new(),
override_color: Color::ByLayer,
annotation_handle: Handle::NULL,
normal: Vector3::UNIT_Z,
horizontal_direction: Vector3::UNIT_X,
block_offset: Vector3::ZERO,
annotation_offset: Vector3::ZERO,
origin: Vector3::ZERO,
dimension_gap: 0.0,
arrowhead_type: 0,
arrow_size: 0.0,
byblock_color: 0,
dwg_unknown_bit1: false,
dwg_unknown_bit2: false,
dwg_unknown_bit3: false,
dwg_unknown_bit4: false,
dwg_unknown_bit5: false,
dwg_unknown_short1: 0,
}
}
pub fn from_vertices(vertices: Vec<Vector3>) -> Self {
let mut leader = Self::new();
leader.vertices = vertices;
leader
}
pub fn two_point(arrow_point: Vector3, end_point: Vector3) -> Self {
let mut leader = Self::new();
leader.vertices.push(arrow_point);
leader.vertices.push(end_point);
leader
}
pub fn with_landing(arrow_point: Vector3, bend_point: Vector3, landing_length: f64) -> Self {
let mut leader = Self::new();
leader.vertices.push(arrow_point);
leader.vertices.push(bend_point);
let landing_end = Vector3::new(bend_point.x + landing_length, bend_point.y, bend_point.z);
leader.vertices.push(landing_end);
leader.hookline_enabled = true;
leader
}
pub fn add_vertex(&mut self, vertex: Vector3) {
self.vertices.push(vertex);
}
pub fn insert_vertex(&mut self, index: usize, vertex: Vector3) {
if index <= self.vertices.len() {
self.vertices.insert(index, vertex);
}
}
pub fn remove_vertex(&mut self, index: usize) -> Option<Vector3> {
if index < self.vertices.len() {
Some(self.vertices.remove(index))
} else {
None
}
}
pub fn vertex_count(&self) -> usize {
self.vertices.len()
}
pub fn arrow_point(&self) -> Option<Vector3> {
self.vertices.first().copied()
}
pub fn end_point(&self) -> Option<Vector3> {
self.vertices.last().copied()
}
pub fn set_arrow_point(&mut self, point: Vector3) {
if self.vertices.is_empty() {
self.vertices.push(point);
} else {
self.vertices[0] = point;
}
}
pub fn length(&self) -> f64 {
if self.vertices.len() < 2 {
return 0.0;
}
let mut total = 0.0;
for i in 0..self.vertices.len() - 1 {
total += self.vertices[i].distance(&self.vertices[i + 1]);
}
total
}
pub fn arrow_direction(&self) -> Option<Vector3> {
if self.vertices.len() < 2 {
return None;
}
let dir = self.vertices[1] - self.vertices[0];
if dir.length_squared() > 0.0 {
Some(dir.normalize())
} else {
None
}
}
pub fn set_path_type(&mut self, path_type: LeaderPathType) {
self.path_type = path_type;
}
pub fn set_arrow_enabled(&mut self, enabled: bool) {
self.arrow_enabled = enabled;
}
pub fn set_hookline_enabled(&mut self, enabled: bool) {
self.hookline_enabled = enabled;
}
pub(crate) fn derived_hookline_enabled(&self) -> bool {
if self.creation_type != LeaderCreationType::WithText
|| self.path_type != LeaderPathType::StraightLine
|| self.vertices.len() < 2
{
return false;
}
let last = self.vertices.len() - 1;
let segment = self.vertices[last] - self.vertices[last - 1];
let axis = self.horizontal_direction;
let denom = segment.length() * axis.length();
if denom <= 1e-12 {
return false;
}
let signed = (segment.dot(&axis) / denom).clamp(-1.0, 1.0);
let mut angle = signed.acos();
if angle > std::f64::consts::FRAC_PI_2 {
angle = std::f64::consts::PI - angle;
}
angle.to_degrees() > 15.0 + 1e-8
}
pub fn set_dimension_style(&mut self, style: impl Into<String>) {
self.dimension_style = style.into();
}
pub fn reverse(&mut self) {
self.vertices.reverse();
}
pub fn clear(&mut self) {
self.vertices.clear();
}
pub fn with_vertex(mut self, vertex: Vector3) -> Self {
self.vertices.push(vertex);
self
}
pub fn with_arrow(mut self, enabled: bool) -> Self {
self.arrow_enabled = enabled;
self
}
pub fn with_spline_path(mut self) -> Self {
self.path_type = LeaderPathType::Spline;
self
}
pub fn with_hookline(mut self) -> Self {
self.hookline_enabled = true;
self
}
pub fn with_dimension_style(mut self, style: impl Into<String>) -> Self {
self.dimension_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
}
pub fn with_creation_type(mut self, creation_type: LeaderCreationType) -> Self {
self.creation_type = creation_type;
self
}
pub fn with_annotation(mut self, handle: Handle) -> Self {
self.annotation_handle = handle;
self
}
}
impl Default for Leader {
fn default() -> Self {
Self::new()
}
}
impl Entity for Leader {
fn common(&self) -> &super::EntityCommon {
&self.common
}
fn common_mut(&mut self) -> &mut super::EntityCommon {
&mut self.common
}
fn bounding_box(&self) -> BoundingBox3D {
BoundingBox3D::from_points(&self.vertices).unwrap_or_default()
}
fn translate(&mut self, offset: Vector3) {
super::translate::translate_leader(self, offset);
}
fn entity_type(&self) -> &'static str {
"LEADER"
}
fn apply_transform(&mut self, transform: &crate::types::Transform) {
super::transform::transform_leader(self, transform);
}
fn apply_mirror(&mut self, transform: &crate::types::Transform) {
super::mirror::mirror_leader(self, transform);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_leader_creation() {
let leader = Leader::new();
assert!(leader.arrow_enabled);
assert_eq!(leader.path_type, LeaderPathType::StraightLine);
assert_eq!(leader.vertex_count(), 0);
}
#[test]
fn test_leader_from_vertices() {
let vertices = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
Vector3::new(20.0, 10.0, 0.0),
];
let leader = Leader::from_vertices(vertices);
assert_eq!(leader.vertex_count(), 3);
}
#[test]
fn test_leader_two_point() {
let leader = Leader::two_point(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 10.0, 0.0));
assert_eq!(leader.vertex_count(), 2);
assert_eq!(leader.arrow_point(), Some(Vector3::new(0.0, 0.0, 0.0)));
assert_eq!(leader.end_point(), Some(Vector3::new(10.0, 10.0, 0.0)));
}
#[test]
fn test_leader_with_landing() {
let leader = Leader::with_landing(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
5.0,
);
assert_eq!(leader.vertex_count(), 3);
assert!(leader.hookline_enabled);
assert_eq!(leader.end_point(), Some(Vector3::new(15.0, 10.0, 0.0)));
}
#[test]
fn test_leader_add_vertex() {
let mut leader = Leader::new();
leader.add_vertex(Vector3::new(0.0, 0.0, 0.0));
leader.add_vertex(Vector3::new(10.0, 10.0, 0.0));
assert_eq!(leader.vertex_count(), 2);
}
#[test]
fn test_leader_insert_remove_vertex() {
let mut leader = Leader::from_vertices(vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(20.0, 0.0, 0.0),
]);
leader.insert_vertex(1, Vector3::new(10.0, 10.0, 0.0));
assert_eq!(leader.vertex_count(), 3);
let removed = leader.remove_vertex(1);
assert_eq!(removed, Some(Vector3::new(10.0, 10.0, 0.0)));
assert_eq!(leader.vertex_count(), 2);
}
#[test]
fn test_leader_length() {
let leader = Leader::from_vertices(vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
]);
assert!((leader.length() - 20.0).abs() < 1e-10);
}
#[test]
fn test_leader_arrow_direction() {
let leader = Leader::from_vertices(vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
]);
let dir = leader.arrow_direction().unwrap();
assert!((dir.x - 1.0).abs() < 1e-10);
assert!(dir.y.abs() < 1e-10);
}
#[test]
fn test_leader_reverse() {
let mut leader = Leader::from_vertices(vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
]);
leader.reverse();
assert_eq!(leader.arrow_point(), Some(Vector3::new(10.0, 10.0, 0.0)));
assert_eq!(leader.end_point(), Some(Vector3::new(0.0, 0.0, 0.0)));
}
#[test]
fn test_leader_translate() {
let mut leader = Leader::from_vertices(vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
]);
leader.translate(Vector3::new(5.0, 5.0, 0.0));
assert_eq!(leader.arrow_point(), Some(Vector3::new(5.0, 5.0, 0.0)));
assert_eq!(leader.end_point(), Some(Vector3::new(15.0, 15.0, 0.0)));
}
#[test]
fn test_leader_path_type() {
assert_eq!(LeaderPathType::from_value(0), LeaderPathType::StraightLine);
assert_eq!(LeaderPathType::from_value(1), LeaderPathType::Spline);
assert_eq!(LeaderPathType::Spline.to_value(), 1);
}
#[test]
fn test_leader_creation_type() {
assert_eq!(
LeaderCreationType::from_value(0),
LeaderCreationType::WithText
);
assert_eq!(
LeaderCreationType::from_value(2),
LeaderCreationType::WithBlock
);
assert_eq!(LeaderCreationType::WithBlock.to_value(), 2);
}
fn text_leader(end: Vector3) -> Leader {
Leader::two_point(Vector3::new(0.0, 0.0, 0.0), end)
}
fn at_degrees(degrees: f64) -> Leader {
let radians = degrees.to_radians();
text_leader(Vector3::new(
100.0 * radians.cos(),
100.0 * radians.sin(),
0.0,
))
}
#[test]
fn r2000_hook_follows_the_15_degree_axis_rule() {
for degrees in [0.0, 10.0, 14.0, 15.0, 165.0, 166.0, 170.0, 180.0, -15.0] {
assert!(
!at_degrees(degrees).derived_hookline_enabled(),
"{degrees}° should not hook"
);
}
for degrees in [15.1, 16.0, 20.0, 45.0, 90.0, 160.0, 164.0, -15.1, -16.0] {
assert!(
at_degrees(degrees).derived_hookline_enabled(),
"{degrees}° should hook"
);
}
}
#[test]
fn r2000_hook_uses_the_leaders_horizontal_direction() {
let mut along_axis = text_leader(Vector3::new(0.0, 100.0, 0.0));
along_axis.horizontal_direction = Vector3::new(0.0, 1.0, 0.0);
assert!(!along_axis.derived_hookline_enabled());
let mut across_axis = text_leader(Vector3::new(100.0, 0.0, 0.0));
across_axis.horizontal_direction = Vector3::new(0.0, 1.0, 0.0);
assert!(across_axis.derived_hookline_enabled());
}
#[test]
fn r2000_hook_requires_a_straight_text_leader() {
let mut spline = at_degrees(45.0);
spline.path_type = LeaderPathType::Spline;
assert!(!spline.derived_hookline_enabled());
let mut block = at_degrees(45.0);
block.creation_type = LeaderCreationType::WithBlock;
assert!(!block.derived_hookline_enabled());
let one_point = Leader::from_vertices(vec![Vector3::new(0.0, 0.0, 0.0)]);
assert!(!one_point.derived_hookline_enabled());
let mut landing = Leader::with_landing(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
20.0,
);
landing.hookline_enabled = false;
assert!(!landing.derived_hookline_enabled());
}
#[test]
fn test_leader_builder() {
let leader = Leader::new()
.with_vertex(Vector3::new(0.0, 0.0, 0.0))
.with_vertex(Vector3::new(10.0, 10.0, 0.0))
.with_hookline()
.with_spline_path()
.with_dimension_style("ISO-25")
.with_layer("LEADERS");
assert_eq!(leader.vertex_count(), 2);
assert!(leader.hookline_enabled);
assert_eq!(leader.path_type, LeaderPathType::Spline);
assert_eq!(leader.dimension_style, "ISO-25");
assert_eq!(leader.common.layer, "LEADERS");
}
#[test]
fn test_leader_bounding_box() {
let leader = Leader::from_vertices(vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
Vector3::new(20.0, 5.0, 0.0),
]);
let bbox = leader.bounding_box();
assert_eq!(bbox.min.x, 0.0);
assert_eq!(bbox.min.y, 0.0);
assert_eq!(bbox.max.x, 20.0);
assert_eq!(bbox.max.y, 10.0);
}
}