use crate::geometry::{Point, Vector2, Line, Arc, Circle};
use crate::data_structure::{Entity, EntityType, EntityGeometry, ObjectId, Transform, Visibility, TextStyle};
use serde::{Serialize, Deserialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum DimensionType {
Linear,
Aligned,
Angular,
Radial,
Diameter,
ArcLength,
Ordinate,
Baseline,
Continued,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DimensionStyle {
pub id: ObjectId,
pub name: String,
pub text_height: f64,
pub arrow_size: f64,
pub arrow_style: ArrowStyle,
pub extension_line_extension: f64,
pub extension_line_offset: f64,
pub dimension_line_gap: f64,
pub text_horizontal_placement: HorizontalTextPlacement,
pub text_vertical_placement: VerticalTextPlacement,
pub text_direction: TextDirection,
pub unit_format: UnitFormat,
pub decimal_places: u32,
pub round_off: f64,
pub prefix: String,
pub suffix: String,
pub alternate_units: bool,
pub alternate_units_factor: f64,
pub tolerance_display: ToleranceDisplay,
pub tolerance_precision: u32,
pub tolerance_upper_value: f64,
pub tolerance_lower_value: f64,
pub text_color: (u8, u8, u8),
pub extension_line_color: (u8, u8, u8),
pub dimension_line_color: (u8, u8, u8),
pub visible: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ArrowStyle {
Closed,
ClosedFilled,
Dot,
SmallClosed,
Open,
OriginIndicator,
Origin02,
Oblique,
ArchitecturalTick,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum HorizontalTextPlacement {
Centered,
Above,
Below,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum VerticalTextPlacement {
Centered,
JIS,
Above,
AboveFromDimensionLine,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum TextDirection {
LeftToRight,
RightToLeft,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum UnitFormat {
Decimal,
Scientific,
Engineering,
Architectural,
Fractional,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ToleranceDisplay {
None,
Symmetrical,
Deviation,
Limits,
Basic,
}
impl Default for DimensionStyle {
fn default() -> Self {
Self {
id: ObjectId::new(),
name: "Standard".to_string(),
text_height: 2.5,
arrow_size: 2.5,
arrow_style: ArrowStyle::ClosedFilled,
extension_line_extension: 1.75,
extension_line_offset: 0.625,
dimension_line_gap: 0.625,
text_horizontal_placement: HorizontalTextPlacement::Centered,
text_vertical_placement: VerticalTextPlacement::Above,
text_direction: TextDirection::LeftToRight,
unit_format: UnitFormat::Decimal,
decimal_places: 2,
round_off: 0.0,
prefix: "".to_string(),
suffix: "".to_string(),
alternate_units: false,
alternate_units_factor: 25.4,
tolerance_display: ToleranceDisplay::None,
tolerance_precision: 2,
tolerance_upper_value: 0.0,
tolerance_lower_value: 0.0,
text_color: (0, 0, 0),
extension_line_color: (0, 0, 0),
dimension_line_color: (0, 0, 0),
visible: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DimensionGeometry {
pub dimension_type: DimensionType,
pub definition_points: Vec<Point>,
pub measurement: f64,
pub text: String,
pub style: DimensionStyle,
pub attachment_point: AttachmentPoint,
pub user_text_location: Option<Point>,
pub text_rotation: f64,
pub actual_measurement: f64,
}
impl DimensionGeometry {
pub fn new(dimension_type: DimensionType, style: DimensionStyle) -> Self {
Self {
dimension_type,
definition_points: Vec::new(),
measurement: 0.0,
text: String::new(),
style,
attachment_point: AttachmentPoint::MiddleCenter,
user_text_location: None,
text_rotation: 0.0,
actual_measurement: 0.0,
}
}
pub fn calculate_measurement(&mut self) {
match self.dimension_type {
DimensionType::Linear | DimensionType::Aligned => {
if self.definition_points.len() >= 2 {
let p1 = self.definition_points[0];
let p2 = self.definition_points[1];
self.measurement = (p2.to_vector2() - p1.to_vector2()).magnitude();
self.actual_measurement = self.measurement;
}
}
DimensionType::Angular => {
if self.definition_points.len() >= 3 {
let center = self.definition_points[0];
let p1 = self.definition_points[1];
let p2 = self.definition_points[2];
let v1 = (p1 - center).to_vector2();
let v2 = (p2 - center).to_vector2();
let angle1 = v1.angle();
let angle2 = v2.angle();
let mut diff = (angle2 - angle1).abs();
if diff > std::f64::consts::PI {
diff = 2.0 * std::f64::consts::PI - diff;
}
self.measurement = diff;
self.actual_measurement = self.measurement;
}
}
DimensionType::Radial | DimensionType::Diameter => {
if self.definition_points.len() >= 2 {
let center = self.definition_points[0];
let p = self.definition_points[1];
let radius = center.distance_to(&p);
if self.dimension_type == DimensionType::Diameter {
self.measurement = radius * 2.0;
} else {
self.measurement = radius;
}
self.actual_measurement = self.measurement;
}
}
_ => {}
}
self.update_text();
}
pub fn update_text(&mut self) {
let formatted_value = self.format_measurement(self.measurement);
let tolerance_text = if self.style.tolerance_display != ToleranceDisplay::None {
format!(
"{:+.*} {} {:+.*}",
self.style.tolerance_precision as usize,
self.style.tolerance_upper_value,
formatted_value,
self.style.tolerance_precision as usize,
self.style.tolerance_lower_value
)
} else {
formatted_value
};
self.text = format!(
"{}{}{}",
self.style.prefix,
tolerance_text,
self.style.suffix
);
}
fn format_measurement(&self, value: f64) -> String {
let rounded = (value / self.style.round_off).round() * self.style.round_off;
match self.style.unit_format {
UnitFormat::Decimal => {
format!("{:.*}", self.style.decimal_places as usize, rounded)
}
UnitFormat::Scientific => {
format!("{:e}", rounded)
}
UnitFormat::Engineering => {
let feet = (rounded / 12.0).floor();
let inches = rounded - feet * 12.0;
format!("{}-{:.*}", feet as u64, self.style.decimal_places as usize, inches)
}
UnitFormat::Fractional => {
let inches = rounded;
let whole = inches.floor();
let fraction = inches - whole;
let denominator = 64;
let numerator = (fraction * denominator as f64).round() as u64;
if numerator == 0 {
format!("{}", whole as u64)
} else {
format!("{}-{}/{}", whole as u64, numerator, denominator)
}
}
_ => {
format!("{:.*}", self.style.decimal_places as usize, rounded)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum AttachmentPoint {
TopLeft,
TopCenter,
TopRight,
MiddleLeft,
MiddleCenter,
MiddleRight,
BottomLeft,
BottomCenter,
BottomRight,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LinearDimension {
pub geometry: DimensionGeometry,
pub extension_line1: Line,
pub extension_line2: Line,
pub dimension_line: Line,
pub arrow1: Point,
pub arrow2: Point,
pub center_mark: Option<CenterMark>,
}
impl LinearDimension {
pub fn new(
definition_line: Line,
style: DimensionStyle,
location: Option<Point>,
) -> Self {
let mut geometry = DimensionGeometry::new(DimensionType::Linear, style.clone());
geometry.definition_points = vec![definition_line.start, definition_line.end];
let direction = (definition_line.end.to_vector2() - definition_line.start.to_vector2()).normalize();
let perpendicular = Vector2::new(-direction.y, direction.x);
let offset = style.extension_line_offset;
let extension = style.extension_line_extension;
let ext1_start = Point::new(
definition_line.start.x - direction.x * offset,
definition_line.start.y - direction.y * offset,
0.0,
);
let ext1_end = Point::new(
definition_line.start.x + direction.x * extension,
definition_line.start.y + direction.y * extension,
0.0,
);
let extension_line1 = Line::new(ext1_start, ext1_end);
let ext2_start = Point::new(
definition_line.end.x - direction.x * offset,
definition_line.end.y - direction.y * offset,
0.0,
);
let ext2_end = Point::new(
definition_line.end.x + direction.x * extension,
definition_line.end.y + direction.y * extension,
0.0,
);
let extension_line2 = Line::new(ext2_start, ext2_end);
let mid_point = definition_line.midpoint();
let dim_line_start = Point::new(
mid_point.x + perpendicular.x * offset * 2.0,
mid_point.y + perpendicular.y * offset * 2.0,
0.0,
);
let dim_line_end = Point::new(
mid_point.x + perpendicular.x * (offset * 2.0 + style.text_height),
mid_point.y + perpendicular.y * (offset * 2.0 + style.text_height),
0.0,
);
let dimension_line = Line::new(dim_line_start, dim_line_end);
geometry.calculate_measurement();
let arrow_offset = style.arrow_size;
let arrow1 = Point::new(
dim_line_start.x + perpendicular.x * arrow_offset,
dim_line_start.y + perpendicular.y * arrow_offset,
0.0,
);
let arrow2 = Point::new(
dim_line_end.x - perpendicular.x * arrow_offset,
dim_line_end.y - perpendicular.y * arrow_offset,
0.0,
);
Self {
geometry,
extension_line1,
extension_line2,
dimension_line,
arrow1,
arrow2,
center_mark: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AlignedDimension {
pub geometry: DimensionGeometry,
pub extension_line1: Line,
pub extension_line2: Line,
pub dimension_line: Line,
pub arrow1: Point,
pub arrow2: Point,
}
impl AlignedDimension {
pub fn new(
p1: Point,
p2: Point,
style: DimensionStyle,
location: Option<Point>,
) -> Self {
let mut geometry = DimensionGeometry::new(DimensionType::Aligned, style.clone());
geometry.definition_points = vec![p1, p2];
let direction = (p2.to_vector2() - p1.to_vector2()).normalize();
let offset = style.extension_line_offset;
let extension = style.extension_line_extension;
let ext1_start = Point::new(
p1.x - direction.y * offset,
p1.y + direction.x * offset,
0.0,
);
let ext1_end = Point::new(
p1.x + direction.y * (extension + offset),
p1.y - direction.x * (extension + offset),
0.0,
);
let extension_line1 = Line::new(ext1_start, ext1_end);
let ext2_start = Point::new(
p2.x - direction.y * offset,
p2.y + direction.x * offset,
0.0,
);
let ext2_end = Point::new(
p2.x + direction.y * (extension + offset),
p2.y - direction.x * (extension + offset),
0.0,
);
let extension_line2 = Line::new(ext2_start, ext2_end);
let mid_point = p1.midpoint(&p2);
let perpendicular = Vector2::new(-direction.y, direction.x);
let dim_line_start = Point::new(
mid_point.x + perpendicular.x * offset * 2.0,
mid_point.y + perpendicular.y * offset * 2.0,
0.0,
);
let dim_line_end = Point::new(
mid_point.x + perpendicular.x * (offset * 2.0 + style.text_height),
mid_point.y + perpendicular.y * (offset * 2.0 + style.text_height),
0.0,
);
let dimension_line = Line::new(dim_line_start, dim_line_end);
geometry.calculate_measurement();
let arrow_offset = style.arrow_size;
let arrow1 = Point::new(
dim_line_start.x + perpendicular.x * arrow_offset,
dim_line_start.y + perpendicular.y * arrow_offset,
0.0,
);
let arrow2 = Point::new(
dim_line_end.x - perpendicular.x * arrow_offset,
dim_line_end.y - perpendicular.y * arrow_offset,
0.0,
);
Self {
geometry,
extension_line1,
extension_line2,
dimension_line,
arrow1,
arrow2,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AngularDimension {
pub geometry: DimensionGeometry,
pub arc: Arc,
pub extension_line1: Line,
pub extension_line2: Line,
pub text_location: Point,
}
impl AngularDimension {
pub fn new(
center: Point,
p1: Point,
p2: Point,
style: DimensionStyle,
location: Option<Point>,
) -> Self {
let mut geometry = DimensionGeometry::new(DimensionType::Angular, style.clone());
geometry.definition_points = vec![center, p1, p2];
let radius = center.distance_to(&p1).max(center.distance_to(&p2));
let v1 = (p1 - center).to_vector2();
let v2 = (p2 - center).to_vector2();
let angle1 = v1.angle();
let angle2 = v2.angle();
let start_angle = angle1.min(angle2);
let end_angle = angle1.max(angle2);
let sweep = (end_angle - start_angle).abs();
let arc = Arc::new(center, radius, start_angle, end_angle);
let ext_length = style.extension_line_extension;
let ext1_start = center;
let ext1_end = Point::new(
center.x + v1.normalize().x * ext_length * 2.0,
center.y + v1.normalize().y * ext_length * 2.0,
0.0,
);
let extension_line1 = Line::new(ext1_start, ext1_end);
let ext2_start = center;
let ext2_end = Point::new(
center.x + v2.normalize().x * ext_length * 2.0,
center.y + v2.normalize().y * ext_length * 2.0,
0.0,
);
let extension_line2 = Line::new(ext2_start, ext2_end);
geometry.calculate_measurement();
let mid_angle = (start_angle + end_angle) / 2.0;
let text_location = Point::new(
center.x + mid_angle.cos() * (radius + style.text_height + style.dimension_line_gap),
center.y + mid_angle.sin() * (radius + style.text_height + style.dimension_line_gap),
0.0,
);
Self {
geometry,
arc,
extension_line1,
extension_line2,
text_location,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RadialDimension {
pub geometry: DimensionGeometry,
pub center_mark: CenterMark,
pub dimension_line: Line,
pub extension_line: Line,
pub arrow: Point,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CenterMark {
pub center: Point,
pub size: f64,
pub mark_type: CenterMarkType,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum CenterMarkType {
None,
Mark,
Cross,
}
impl RadialDimension {
pub fn new(
circle: Circle,
p: Point,
style: DimensionStyle,
is_diameter: bool,
) -> Self {
let dim_type = if is_diameter { DimensionType::Diameter } else { DimensionType::Radial };
let mut geometry = DimensionGeometry::new(dim_type, style.clone());
geometry.definition_points = vec![circle.center, p];
let radius = circle.center.distance_to(&p);
if is_diameter {
geometry.measurement = radius * 2.0;
} else {
geometry.measurement = radius;
}
geometry.actual_measurement = geometry.measurement;
geometry.update_text();
let center_mark_size = style.arrow_size * 2.0;
let center_mark = CenterMark {
center: circle.center,
size: center_mark_size,
mark_type: CenterMarkType::Cross,
};
let direction = (p.to_vector2() - circle.center.to_vector2()).normalize();
let offset = style.arrow_size;
let arrow = Point::new(
p.x - direction.x * offset,
p.y - direction.y * offset,
0.0,
);
let dim_line_start = Point::new(
circle.center.x + direction.x * radius,
circle.center.y + direction.y * radius,
0.0,
);
let dim_line_end = Point::new(
circle.center.x + direction.x * (radius + style.text_height + style.dimension_line_gap),
circle.center.y + direction.y * (radius + style.text_height + style.dimension_line_gap),
0.0,
);
let dimension_line = Line::new(dim_line_start, dim_line_end);
let extension_line = Line::new(p, dim_line_start);
Self {
geometry,
center_mark,
dimension_line,
extension_line,
arrow,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OrdinateDimension {
pub geometry: DimensionGeometry,
pub feature_point: Point,
pub leader_point: Point,
pub dimension_line: Line,
pub orientation: OrdinateOrientation,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum OrdinateOrientation {
X,
Y,
}
impl OrdinateDimension {
pub fn new(
feature_point: Point,
leader_point: Point,
style: DimensionStyle,
orientation: OrdinateOrientation,
) -> Self {
let dim_type = match orientation {
OrdinateOrientation::X => DimensionType::Ordinate,
_ => DimensionType::Ordinate,
};
let mut geometry = DimensionGeometry::new(dim_type, style.clone());
geometry.definition_points = vec![feature_point, leader_point];
match orientation {
OrdinateOrientation::X => {
geometry.measurement = feature_point.x;
}
OrdinateOrientation::Y => {
geometry.measurement = feature_point.y;
}
}
geometry.actual_measurement = geometry.measurement;
geometry.update_text();
let dimension_line = Line::new(feature_point, leader_point);
Self {
geometry,
feature_point,
leader_point,
dimension_line,
orientation,
}
}
}
impl From<LinearDimension> for Entity {
fn from(dim: LinearDimension) -> Self {
Entity::new(
EntityType::Dimension,
EntityGeometry::Dimension(DimensionGeometryData::Linear(dim)),
)
}
}
impl From<AlignedDimension> for Entity {
fn from(dim: AlignedDimension) -> Self {
Entity::new(
EntityType::Dimension,
EntityGeometry::Dimension(DimensionGeometryData::Aligned(dim)),
)
}
}
impl From<AngularDimension> for Entity {
fn from(dim: AngularDimension) -> Self {
Entity::new(
EntityType::Dimension,
EntityGeometry::Dimension(DimensionGeometryData::Angular(dim)),
)
}
}
impl From<RadialDimension> for Entity {
fn from(dim: RadialDimension) -> Self {
Entity::new(
EntityType::Dimension,
EntityGeometry::Dimension(DimensionGeometryData::Radial(dim)),
)
}
}
impl From<OrdinateDimension> for Entity {
fn from(dim: OrdinateDimension) -> Self {
Entity::new(
EntityType::Dimension,
EntityGeometry::Dimension(DimensionGeometryData::Ordinate(dim)),
)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DimensionGeometryData {
Linear(LinearDimension),
Aligned(AlignedDimension),
Angular(AngularDimension),
Radial(RadialDimension),
Ordinate(OrdinateDimension),
}