Compare commits

...

12 Commits

@ -1,5 +1,7 @@
use super::{two_dec, ScaleEntity}; use super::{two_dec, ScaleEntity};
use dxf::entities; use dxf::entities;
use dxf::entities::LwPolyline;
use hex_color::HexColor;
use simple_xml_builder::XMLElement; use simple_xml_builder::XMLElement;
#[derive(Debug)] #[derive(Debug)]
@ -18,37 +20,71 @@ pub struct Arc {
impl From<&entities::Arc> for Arc { impl From<&entities::Arc> for Arc {
fn from(arc: &entities::Arc) -> Self { fn from(arc: &entities::Arc) -> Self {
let temp_angle = if arc.start_angle > arc.end_angle { Self::from_arc_with_color(arc, HexColor::BLACK)
(360.0 - arc.start_angle) + arc.end_angle }
}
impl Arc {
/// 从DXF Arc实体创建Arc支持自定义颜色
pub fn from_arc_with_color(arc: &entities::Arc, color: HexColor) -> Self {
let is_mirror = arc.normal.z < 0.0;
let (start_angle, angle_span) = if is_mirror {
// 镜像变换角度关于Y轴对称即 θ -> 180° - θ
// 同时交换起始和结束角度以保持正确的绘制方向
let mirrored_start = 180.0 - arc.end_angle;
let mirrored_end = 180.0 - arc.start_angle;
// 标准化角度到0-360度范围
let norm_start = if mirrored_start < 0.0 {
mirrored_start + 360.0
} else {
mirrored_start
};
let norm_end = if mirrored_end < 0.0 {
mirrored_end + 360.0
} else { } else {
mirrored_end
};
// 计算角度跨度
let span = if norm_end >= norm_start {
norm_end - norm_start
} else {
(360.0 - norm_start) + norm_end
};
(norm_start, span)
} else {
// 正常情况:直接使用原始角度
let span = if arc.end_angle >= arc.start_angle {
arc.end_angle - arc.start_angle arc.end_angle - arc.start_angle
} else {
(360.0 - arc.start_angle) + arc.end_angle
};
(arc.start_angle, span)
}; };
Arc { Arc {
x: arc.center.x - arc.radius, x: if is_mirror {
-arc.center.x - arc.radius
} else {
arc.center.x - arc.radius
},
y: -arc.center.y - arc.radius, y: -arc.center.y - arc.radius,
height: arc.radius * 2.0, height: arc.radius * 2.0,
width: arc.radius * 2.0, width: arc.radius * 2.0,
start: if arc.start_angle < 0.0 { start: if start_angle < 0.0 { -start_angle } else { start_angle },
-arc.start_angle angle: if angle_span < 0.0 { -angle_span } else { angle_span },
} else {
arc.start_angle
},
angle: if temp_angle < 0.0 {
-temp_angle
} else {
temp_angle
},
//in the original code antialias is always set to false...I'm guessing for performance //in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on? //reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false, antialias: false,
style: if arc.thickness > 0.1 { style: if arc.thickness > 0.1 {
"line-style:normal;line-weight:normal;filling:none;color:black" format!("line-style:normal;line-weight:normal;filling:none;color:{}", color.display_rgb())
} else { } else {
"line-style:normal;line-weight:thin;filling:none;color:black" format!("line-style:normal;line-weight:thin;filling:none;color:{}", color.display_rgb())
} },
.into(),
} }
} }
} }
@ -68,6 +104,28 @@ impl From<&Arc> for XMLElement {
} }
} }
impl Arc {
pub fn new(x: f64, y: f64, width: f64, height: f64, start: f64, angle: f64, style: String) -> Self {
Arc {
x,
y,
width,
height,
start,
angle,
style,
antialias: false,
}
}
pub fn update_line_style<F>(&mut self, update_fn: F)
where
F: FnOnce(&mut String),
{
update_fn(&mut self.style);
}
}
impl ScaleEntity for Arc { impl ScaleEntity for Arc {
fn scale(&mut self, fact_x: f64, fact_y: f64) { fn scale(&mut self, fact_x: f64, fact_y: f64) {
self.x *= fact_x; self.x *= fact_x;
@ -92,3 +150,107 @@ impl ScaleEntity for Arc {
self.y + self.height self.y + self.height
} }
} }
// 在 mod.rs 中添加从 LwPolyline 转换为 Arc 的方法
impl TryFrom<&LwPolyline> for Arc {
type Error = String;
fn try_from(lwpolyline: &LwPolyline) -> Result<Self, Self::Error> {
Self::try_from_lwpolyline_with_color(lwpolyline, HexColor::BLACK)
}
}
impl Arc {
/// 从DXF LwPolyline实体创建Arc支持自定义颜色
pub fn try_from_lwpolyline_with_color(lwpolyline: &LwPolyline, color: HexColor) -> Result<Self, String> {
// 检查顶点数量必须是2个顶点才能形成圆弧
if lwpolyline.vertices.len() != 2 {
return Err(format!("Arc conversion requires exactly 2 vertices, got {}", lwpolyline.vertices.len()));
}
let v1 = &lwpolyline.vertices[0];
let v2 = &lwpolyline.vertices[1];
// 检查第一个顶点是否有bulge值
if v1.bulge == 0.0 {
return Err("No bulge value found for arc conversion".to_string());
}
// 计算弦长和中点
let chord_length = ((v2.x - v1.x).powi(2) + (v2.y - v1.y).powi(2)).sqrt();
let mid_x = (v1.x + v2.x) / 2.0;
let mid_y = (v1.y + v2.y) / 2.0;
// 根据bulge值计算圆弧参数
// bulge = tan(angle/4)其中angle是圆弧的包含角
let bulge = v1.bulge;
let included_angle = 4.0 * bulge.atan(); // 圆弧包含角(弧度)
// 计算半径
let radius = chord_length / (2.0 * (included_angle / 2.0).sin());
// 计算圆心位置
// 弦的方向向量
let chord_dx = v2.x - v1.x;
let chord_dy = v2.y - v1.y;
// 弦的垂直方向向左旋转90度
let perp_dx = -chord_dy;
let perp_dy = chord_dx;
let perp_length = (perp_dx * perp_dx + perp_dy * perp_dy).sqrt();
// 标准化垂直向量
let unit_perp_x = perp_dx / perp_length;
let unit_perp_y = perp_dy / perp_length;
// 计算圆心到弦中点的距离
let center_distance = radius * (included_angle / 2.0).cos();
// 根据bulge的符号确定圆心位置
let center_x = if bulge > 0.0 {
mid_x + center_distance * unit_perp_x
} else {
mid_x - center_distance * unit_perp_x
};
let center_y = if bulge > 0.0 {
mid_y + center_distance * unit_perp_y
} else {
mid_y - center_distance * unit_perp_y
};
// 计算起始角度和结束角度
let start_angle = (v1.y - center_y).atan2(v1.x - center_x);
let end_angle = (v2.y - center_y).atan2(v2.x - center_x);
// 将弧度转换为度数
let mut start_angle_deg = start_angle.to_degrees();
let mut end_angle_deg = end_angle.to_degrees();
// 标准化角度到0-360度范围
if start_angle_deg < 0.0 {
start_angle_deg += 360.0;
}
if end_angle_deg < 0.0 {
end_angle_deg += 360.0;
}
// 计算角度跨度
let arc_angle = included_angle.to_degrees().abs();
// 创建Arc对象
Ok(Arc {
x: center_x - radius, // 边界框左上角x坐标
y: -center_y - radius, // 边界框左上角y坐标y轴翻转
width: radius * 2.0, // 边界框宽度
height: radius * 2.0, // 边界框高度
start: start_angle_deg, // 起始角度(度)
angle: arc_angle, // 圆弧角度跨度(度)
style: if lwpolyline.thickness > 0.1 {
format!("line-style:normal;line-weight:normal;filling:none;color:{}", color.display_rgb())
} else {
format!("line-style:normal;line-weight:thin;filling:none;color:{}", color.display_rgb())
},
antialias: false,
})
}
}

@ -4,6 +4,8 @@ use hex_color::HexColor;
use simple_xml_builder::XMLElement; use simple_xml_builder::XMLElement;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use uuid::Uuid; use uuid::Uuid;
use dxf::enums::AttachmentPoint;
use dxf::enums::{HorizontalTextJustification, VerticalTextJustification};
/*use parley::{ /*use parley::{
Alignment, FontContext, FontWeight, InlineBox, Layout, LayoutContext, PositionedLayoutItem, Alignment, FontContext, FontWeight, InlineBox, Layout, LayoutContext, PositionedLayoutItem,
@ -59,7 +61,26 @@ impl From<&DynamicText> for XMLElement {
// o.k. ... as long as we do not know the real width: // o.k. ... as long as we do not know the real width:
// "guess" the width by number of characters and font-size: // "guess" the width by number of characters and font-size:
// //
let graphene_count = txt.text.graphemes(true).count(); // let graphene_count = txt.text.graphemes(true).count();
let mut cjk_char_count = 0;
let mut graphene_count = 0;
for grapheme in txt.text.graphemes(true) {
graphene_count += 1;
if grapheme.chars().any(|c| {
// 检查是否为中文字符(包括中日韩统一表意文字)
(c >= '\u{4E00}' && c <= '\u{9FFF}') || // CJK统一表意文字
(c >= '\u{3400}' && c <= '\u{4DBF}') || // CJK扩展A
(c >= '\u{20000}' && c <= '\u{2A6DF}') || // CJK扩展B
(c >= '\u{2A700}' && c <= '\u{2B73F}') || // CJK扩展C
(c >= '\u{2B740}' && c <= '\u{2B81F}') || // CJK扩展D
(c >= '\u{2B820}' && c <= '\u{2CEAF}') || // CJK扩展E
(c >= '\u{F900}' && c <= '\u{FAFF}') || // CJK兼容表意文字
(c >= '\u{2F800}' && c <= '\u{2FA1F}') // CJK兼容表意文字补充
}) {
cjk_char_count += 1;
}
}
let txt_width = if txt.reference_rectangle_width > 2.0 { let txt_width = if txt.reference_rectangle_width > 2.0 {
txt.reference_rectangle_width txt.reference_rectangle_width
} else { } else {
@ -85,15 +106,44 @@ impl From<&DynamicText> for XMLElement {
// } // }
// }; // };
// 如果采用计算对齐的方式,可能还要考虑字体宽度的偏差。。
println!("文本:{}", txt.text);
println!("对齐方式:{}, {}", txt.h_alignment, txt.v_alignment);
println!("文本宽度: {}", txt_width);
println!("文本高度:{}", pt_size);
println!("初始位置: x={}, y={}", txt.x, txt.y);
println!("旋转角度: {}", txt.rotation);
println!("cjk_char_count: {}", cjk_char_count);
// 计算基础位置(不考虑旋转) // 计算基础位置(不考虑旋转)
let base_x = txt.x + 0.5 - (pt_size / 8.0) - 4.05; // txt.x和txt.y现在是对齐点坐标需要根据对齐方式计算出左上角位置
let base_y = { // 首先根据水平对齐方式计算左边界的x坐标
let base_y_pos = txt.y + 0.5 - (7.0 / 5.0 * pt_size + 26.0 / 5.0) + pt_size; let left_x = match txt.h_alignment {
match txt.v_alignment { HAlignment::Left => txt.x,
VAlignment::Top => base_y_pos, HAlignment::Center => txt.x - txt_width / 2.0,
VAlignment::Center => base_y_pos - pt_size / 2.0, HAlignment::Right => txt.x - txt_width,
VAlignment::Bottom => base_y_pos - pt_size, };
}
// 根据垂直对齐方式计算顶部的y坐标
let top_y = txt.y;
// 根据是否包含中文字符调整字体大小相关的偏移量
let cjk_offset:f64 = (cjk_char_count as f64) * pt_size * 0.75;
// 左对齐是不需要中文偏移的,居中对齐需要偏移,右对齐要偏移
let base_x = left_x + 0.5 - (pt_size / 8.0) - 4.05
- match txt.h_alignment {
HAlignment::Left => 0.0,
HAlignment::Center => cjk_offset / 2.0,
HAlignment::Right => cjk_offset,
};
let base_y = top_y + 0.5 - (7.0 / 5.0 * pt_size + 26.0 / 5.0)
+ match txt.v_alignment {
VAlignment::Top => pt_size / 2.0,
VAlignment::Center => pt_size,
VAlignment::Bottom => pt_size / 2.0,
}; };
// 如果有旋转角度,应用旋转变换 // 如果有旋转角度,应用旋转变换
@ -102,7 +152,7 @@ impl From<&DynamicText> for XMLElement {
let cos_r = rotation_rad.cos(); let cos_r = rotation_rad.cos();
let sin_r = rotation_rad.sin(); let sin_r = rotation_rad.sin();
// 以文本原点为中心进行旋转变换 // 以对齐点为中心进行旋转变换
let dx = base_x - txt.x; let dx = base_x - txt.x;
let dy = base_y - txt.y; let dy = base_y - txt.y;
@ -114,6 +164,9 @@ impl From<&DynamicText> for XMLElement {
(base_x, base_y) (base_x, base_y)
}; };
println!("最终位置: x={}, y={}", x_pos, y_pos);
dtxt_xml.add_attribute("x", two_dec(x_pos)); dtxt_xml.add_attribute("x", two_dec(x_pos));
dtxt_xml.add_attribute("y", two_dec(y_pos)); dtxt_xml.add_attribute("y", two_dec(y_pos));
dtxt_xml.add_attribute("z", two_dec(txt.z)); dtxt_xml.add_attribute("z", two_dec(txt.z));
@ -136,6 +189,12 @@ impl From<&DynamicText> for XMLElement {
text_xml.add_text(&txt.text); text_xml.add_text(&txt.text);
dtxt_xml.add_child(text_xml); dtxt_xml.add_child(text_xml);
// 添加颜色
let mut color_xml = XMLElement::new("color");
color_xml.add_text(&txt.color.display_rgb());
dtxt_xml.add_child(color_xml);
if let Some(i_name) = &txt.info_name { if let Some(i_name) = &txt.info_name {
dtxt_xml.add_attribute("info_name", i_name); dtxt_xml.add_attribute("info_name", i_name);
} }
@ -222,9 +281,15 @@ impl<'a> DTextBuilder<'a> {
v_alignment, v_alignment,
reference_rectangle_width, reference_rectangle_width,
) = match self.text { ) = match self.text {
TextEntity::Text(txt) => ( TextEntity::Text(txt) => {
txt.location.x, let (x, y) = if txt.horizontal_text_justification == HorizontalTextJustification::Left && (txt.vertical_text_justification == VerticalTextJustification::Bottom || txt.vertical_text_justification == VerticalTextJustification::Baseline) {
-txt.location.y, (txt.location.x, -txt.location.y)
} else {
(txt.second_alignment_point.x, -txt.second_alignment_point.y)
};
(
x,
y,
txt.location.z, txt.location.z,
txt.rotation, txt.rotation,
&txt.text_style_name, &txt.text_style_name,
@ -233,12 +298,15 @@ impl<'a> DTextBuilder<'a> {
HAlignment::from(txt.horizontal_text_justification), HAlignment::from(txt.horizontal_text_justification),
VAlignment::from(txt.vertical_text_justification), VAlignment::from(txt.vertical_text_justification),
0.0, // as Placeholder: no "reference_rectangle_width" with Text!!! 0.0, // as Placeholder: no "reference_rectangle_width" with Text!!!
), )},
TextEntity::MText(mtxt) => ( TextEntity::MText(mtxt) => {
// 计算实际的旋转角度优先使用x_axis_direction向量
let actual_rotation = calculate_mtext_rotation(mtxt.rotation_angle, &mtxt.x_axis_direction);
(
mtxt.insertion_point.x, mtxt.insertion_point.x,
-mtxt.insertion_point.y, -mtxt.insertion_point.y,
mtxt.insertion_point.z, mtxt.insertion_point.z,
mtxt.rotation_angle, actual_rotation,
&mtxt.text_style_name, &mtxt.text_style_name,
//I'm not sure what the proper value is here for Mtext //I'm not sure what the proper value is here for Mtext
//becuase I haven't actually finished supporting it. //becuase I haven't actually finished supporting it.
@ -262,15 +330,25 @@ impl<'a> DTextBuilder<'a> {
{ {
let mut val = mtxt.extended_text.join(""); let mut val = mtxt.extended_text.join("");
val.push_str(&mtxt.text); val.push_str(&mtxt.text);
val.replace("\\P", "\n") let processed_val = val.replace("\\P", "\n");
super::extract_text_from_pcl(&processed_val)
}, },
HAlignment::from(mtxt.attachment_point), HAlignment::from(mtxt.attachment_point),
VAlignment::from(mtxt.attachment_point), VAlignment::from(mtxt.attachment_point),
mtxt.reference_rectangle_width, mtxt.reference_rectangle_width,
), )},
TextEntity::Attrib(attrib) => ( TextEntity::Attrib(attrib) => {
attrib.location.x, // 根据DXF文本对齐规则当水平对齐不是Left时使用second_alignment_point
-attrib.location.y, let (x, y) = if attrib.horizontal_text_justification == HorizontalTextJustification::Left && (attrib.vertical_text_justification == VerticalTextJustification::Bottom || attrib.vertical_text_justification == VerticalTextJustification::Baseline) {
println!("{}",111);
(attrib.location.x, -attrib.location.y)
} else {
println!("{}",222);
(attrib.second_alignment_point.x, -attrib.second_alignment_point.y)
};
(
x,
y,
attrib.location.z, attrib.location.z,
attrib.rotation, attrib.rotation,
&attrib.text_style_name, &attrib.text_style_name,
@ -279,7 +357,8 @@ impl<'a> DTextBuilder<'a> {
HAlignment::from(attrib.horizontal_text_justification), HAlignment::from(attrib.horizontal_text_justification),
VAlignment::from(attrib.vertical_text_justification), VAlignment::from(attrib.vertical_text_justification),
0.0, // as Placeholder: not need to check if Attrib has something similar 0.0, // as Placeholder: not need to check if Attrib has something similar
), )
},
}; };
// Create a FontContext (font database) and LayoutContext (scratch space). // Create a FontContext (font database) and LayoutContext (scratch space).
@ -351,3 +430,86 @@ impl<'a> DTextBuilder<'a> {
} }
} }
} }
/// 计算MText的实际旋转角度
///
/// # Arguments
/// * `rotation_angle` - DXF中的rotation_angle字段编号50
/// * `x_axis_direction` - DXF中的x_axis_direction向量编号11,21,31
///
/// # Returns
/// 返回实际的旋转角度(弧度)
fn calculate_mtext_rotation(rotation_angle: f64, x_axis_direction: &dxf::Vector) -> f64 {
// 检查x_axis_direction是否为默认值(1.0, 0.0, 0.0)
let default_x_axis = (x_axis_direction.x - 1.0).abs() < 1e-10
&& x_axis_direction.y.abs() < 1e-10
&& x_axis_direction.z.abs() < 1e-10;
if default_x_axis {
// 如果x_axis_direction是默认值使用rotation_angle
rotation_angle
} else {
// 如果x_axis_direction不是默认值从向量计算角度
// atan2返回弧度值需要转换为度数
let angle_radians = x_axis_direction.y.atan2(x_axis_direction.x);
// 将弧度转换为度数这里rotation字段期望度数
angle_radians * 180.0 / std::f64::consts::PI
}
}
/// 将MText的对齐点坐标调整为左下角坐标
///
/// # 参数
/// * `x` - MText的insertion_point.x对齐点X坐标
/// * `y` - MText的insertion_point.y对齐点Y坐标
/// * `attachment_point` - MText的对齐方式
/// * `text_height` - 文本高度
/// * `text_width` - 文本宽度reference_rectangle_width
///
/// # 返回值
/// 返回调整后的(x, y)坐标,对应文本的左下角位置
pub fn adjust_mtext_coordinates(
x: f64,
y: f64,
attachment_point: AttachmentPoint,
text_height: f64,
text_width: f64,
) -> (f64, f64) {
let mut adjusted_x = x;
let mut adjusted_y = y;
// 根据水平对齐方式调整X坐标
match attachment_point {
// 左对齐无需调整X坐标
AttachmentPoint::TopLeft | AttachmentPoint::MiddleLeft | AttachmentPoint::BottomLeft => {
// X坐标已经是左边界无需调整
}
// 中心对齐:向左偏移文本宽度的一半
AttachmentPoint::TopCenter | AttachmentPoint::MiddleCenter | AttachmentPoint::BottomCenter => {
adjusted_x -= text_width / 2.0; // 如果文本旋转了,那就不是这么算了
}
// 右对齐:向左偏移整个文本宽度
AttachmentPoint::TopRight | AttachmentPoint::MiddleRight | AttachmentPoint::BottomRight => {
adjusted_x -= text_width;
}
}
// 根据垂直对齐方式调整Y坐标
match attachment_point {
// 底部对齐无需调整Y坐标已经是底边界
AttachmentPoint::BottomLeft | AttachmentPoint::BottomCenter | AttachmentPoint::BottomRight => {
// Y坐标已经是底边界无需调整
}
// 中心对齐:向下偏移文本高度的一半
AttachmentPoint::MiddleLeft | AttachmentPoint::MiddleCenter | AttachmentPoint::MiddleRight => {
adjusted_y -= text_height / 2.0;
}
// 顶部对齐:向下偏移整个文本高度
AttachmentPoint::TopLeft | AttachmentPoint::TopCenter | AttachmentPoint::TopRight => {
adjusted_y -= text_height;
}
}
(adjusted_x, adjusted_y)
}

@ -1,5 +1,6 @@
use super::{two_dec, Circularity, ScaleEntity}; use super::{two_dec, Circularity, ScaleEntity};
use dxf::entities::{self, Circle, LwPolyline, Polyline}; use dxf::entities::{self, Circle, LwPolyline, Polyline};
use hex_color::HexColor;
use simple_xml_builder::XMLElement; use simple_xml_builder::XMLElement;
#[derive(Debug)] #[derive(Debug)]
@ -7,6 +8,7 @@ pub struct Ellipse {
height: f64, height: f64,
width: f64, width: f64,
style: String, style: String,
color: HexColor,
//need to brush up on my Rust scoping rules, isn't there a way to make this pub to just the module? //need to brush up on my Rust scoping rules, isn't there a way to make this pub to just the module?
pub x: f64, pub x: f64,
@ -22,6 +24,7 @@ impl From<&Circle> for Ellipse {
y: -circ.center.y - circ.radius, y: -circ.center.y - circ.radius,
height: circ.radius * 2.0, height: circ.radius * 2.0,
width: circ.radius * 2.0, width: circ.radius * 2.0,
color: HexColor::BLACK,
//in the original code antialias is always set to false...I'm guessing for performance //in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on? //reasons...I'm trying to think if there is a time we might want to turn it on?
@ -41,8 +44,9 @@ impl From<&entities::Ellipse> for Ellipse {
Ellipse { Ellipse {
x: ellipse.center.x - ellipse.major_axis.x, x: ellipse.center.x - ellipse.major_axis.x,
y: -ellipse.center.y - ellipse.major_axis.x * ellipse.minor_axis_ratio, y: -ellipse.center.y - ellipse.major_axis.x * ellipse.minor_axis_ratio,
height: ellipse.major_axis.x * 2.0, width: ellipse.major_axis.x * 2.0,
width: ellipse.major_axis.x * 2.0 * ellipse.minor_axis_ratio, height: ellipse.major_axis.x * 2.0 * ellipse.minor_axis_ratio,
color: HexColor::BLACK,
//in the original code antialias is always set to false...I'm guessing for performance //in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on? //reasons...I'm trying to think if there is a time we might want to turn it on?
@ -87,6 +91,7 @@ impl TryFrom<&Polyline> for Ellipse {
y: -max_y, y: -max_y,
height: max_y - y, height: max_y - y,
width: max_x - x, width: max_x - x,
color: HexColor::BLACK,
//in the original code antialias is always set to false...I'm guessing for performance //in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on? //reasons...I'm trying to think if there is a time we might want to turn it on?
@ -100,7 +105,15 @@ impl TryFrom<&LwPolyline> for Ellipse {
type Error = &'static str; //add better error later type Error = &'static str; //add better error later
fn try_from(poly: &LwPolyline) -> Result<Self, Self::Error> { fn try_from(poly: &LwPolyline) -> Result<Self, Self::Error> {
if !poly.is_circular() { Self::try_from_lwpolyline_with_color(poly, HexColor::BLACK)
}
}
impl Ellipse {
pub fn try_from_lwpolyline_with_color(poly: &LwPolyline, color: HexColor) -> Result<Self, &'static str> {
let is_bugle_circle = poly.is_circular_with_bulge();
if !is_bugle_circle&&!poly.is_circular() {
return Err("Polyline has poor circularity, can't convert"); return Err("Polyline has poor circularity, can't convert");
} }
@ -124,16 +137,23 @@ impl TryFrom<&LwPolyline> for Ellipse {
.iter() .iter()
.fold(f64::MIN, |max_y, vtx| max_y.max(vtx.y)); .fold(f64::MIN, |max_y, vtx| max_y.max(vtx.y));
let style = format!("line-style:normal;line-weight:thin;filling:none;color:rgb({},{},{})", color.r, color.g, color.b);
Ok(Ellipse { Ok(Ellipse {
x, x,
y: -max_y, y: if is_bugle_circle {-max_y-(max_x - x)/2.0} else {-max_y},
height: max_y - y, height: if is_bugle_circle {
max_x - x
} else {
max_y - y
},
width: max_x - x, width: max_x - x,
color,
//in the original code antialias is always set to false...I'm guessing for performance //in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on? //reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false, antialias: false,
style: "line-style:normal;line-weight:thin;filling:none;color:black".into(), style,
}) })
} }
} }
@ -147,10 +167,32 @@ impl From<&Ellipse> for XMLElement {
ell_xml.add_attribute("height", two_dec(ell.height)); ell_xml.add_attribute("height", two_dec(ell.height));
ell_xml.add_attribute("antialias", ell.antialias); ell_xml.add_attribute("antialias", ell.antialias);
ell_xml.add_attribute("style", &ell.style); ell_xml.add_attribute("style", &ell.style);
ell_xml.add_attribute("color", ell.color.display_rgb().to_string());
ell_xml ell_xml
} }
} }
impl Ellipse {
pub fn set_style(&mut self, style: String) {
self.style = style;
}
pub fn update_line_style<F>(&mut self, update_fn: F)
where
F: FnOnce(&mut String),
{
update_fn(&mut self.style);
}
pub fn set_color(&mut self, color: HexColor) {
self.color = color;
}
pub fn get_color(&self) -> HexColor {
self.color
}
}
impl ScaleEntity for Ellipse { impl ScaleEntity for Ellipse {
fn scale(&mut self, fact_x: f64, fact_y: f64) { fn scale(&mut self, fact_x: f64, fact_y: f64) {
self.x *= fact_x; self.x *= fact_x;

@ -2,6 +2,7 @@ use super::two_dec;
use super::LineEnd; use super::LineEnd;
use super::ScaleEntity; use super::ScaleEntity;
use dxf::entities::{self, LwPolyline, Polyline}; use dxf::entities::{self, LwPolyline, Polyline};
use hex_color::HexColor;
use simple_xml_builder::XMLElement; use simple_xml_builder::XMLElement;
#[derive(Debug)] #[derive(Debug)]
@ -25,26 +26,7 @@ pub struct Leader(pub Vec<Line>);
impl From<&entities::Line> for Line { impl From<&entities::Line> for Line {
fn from(line: &entities::Line) -> Self { fn from(line: &entities::Line) -> Self {
Line { Self::from_line_with_color(line, HexColor::BLACK)
x1: line.p1.x,
y1: -line.p1.y,
length1: 1.5, //why is this statically set at 1.5?
end1: LineEnd::None,
x2: line.p2.x,
y2: -line.p2.y,
length2: 1.5, //why is this statically set at 1.5?
end2: LineEnd::None,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if line.thickness > 0.5 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),
}
} }
} }
@ -83,6 +65,13 @@ impl TryFrom<&LwPolyline> for Line {
type Error = &'static str; //add better error later type Error = &'static str; //add better error later
fn try_from(poly: &LwPolyline) -> Result<Self, Self::Error> { fn try_from(poly: &LwPolyline) -> Result<Self, Self::Error> {
Self::try_from_lwpolyline_with_color(poly, HexColor::BLACK)
}
}
impl Line {
/// 从DXF LwPolyline实体创建Line支持自定义颜色
pub fn try_from_lwpolyline_with_color(poly: &LwPolyline, color: HexColor) -> Result<Self, &'static str> {
if poly.vertices.len() != 2 { if poly.vertices.len() != 2 {
return Err("Error can't convert polyline with more than 2 points into a Line"); return Err("Error can't convert polyline with more than 2 points into a Line");
} }
@ -101,11 +90,12 @@ impl TryFrom<&LwPolyline> for Line {
//reasons...I'm trying to think if there is a time we might want to turn it on? //reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false, antialias: false,
style: if poly.thickness > 0.1 { style: if poly.thickness > 0.1 {
"line-style:normal;line-weight:normal;filling:none;color:black" format!("line-style:normal;line-weight:normal;filling:none;color:{}", color.display_rgb())
} else if poly.constant_width > 1.0{
format!("line-style:normal;line-weight:hight;filling:none;color:{}", color.display_rgb())
} else { } else {
"line-style:normal;line-weight:thin;filling:none;color:black" format!("line-style:normal;line-weight:thin;filling:none;color:{}", color.display_rgb())
} },
.into(),
}) })
} }
} }
@ -171,6 +161,53 @@ impl From<&Line> for XMLElement {
} }
} }
impl Line {
/// 从DXF Line实体创建Line支持自定义颜色
pub fn from_line_with_color(line: &entities::Line, color: HexColor) -> Self {
Line {
x1: line.p1.x,
y1: -line.p1.y,
length1: 1.5, //why is this statically set at 1.5?
end1: LineEnd::None,
x2: line.p2.x,
y2: -line.p2.y,
length2: 1.5, //why is this statically set at 1.5?
end2: LineEnd::None,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if line.thickness > 0.5 {
format!("line-style:normal;line-weight:normal;filling:none;color:{}", color.display_rgb())
} else {
format!("line-style:normal;line-weight:thin;filling:none;color:{}", color.display_rgb())
},
}
}
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64, style: String) -> Self {
Line {
length2: 1.5,
end2: LineEnd::None,
length1: 1.5,
x1,
y1,
x2,
y2,
style,
end1: LineEnd::None,
antialias: false,
}
}
pub fn update_line_style<F>(&mut self, update_fn: F)
where
F: FnOnce(&mut String),
{
update_fn(&mut self.style);
}
}
impl ScaleEntity for Line { impl ScaleEntity for Line {
fn scale(&mut self, fact_x: f64, fact_y: f64) { fn scale(&mut self, fact_x: f64, fact_y: f64) {
self.x1 *= fact_x; self.x1 *= fact_x;

@ -100,6 +100,9 @@ trait Circularity {
//for a specific type //for a specific type
0.98..=1.02 0.98..=1.02
} }
// 考虑bugle圆形判断
fn is_circular_with_bulge(&self) -> bool;
} }
// 为 Polyline 类型实现圆形检测,有点类似函数定义 // 为 Polyline 类型实现圆形检测,有点类似函数定义
@ -137,6 +140,10 @@ impl Circularity for Polyline {
Self::match_range().contains(&t_ratio) Self::match_range().contains(&t_ratio)
} }
fn is_circular_with_bulge(&self) -> bool{
false
}
} }
// 为 LwPolyline 类型实现相同的圆形检测逻辑 // 为 LwPolyline 类型实现相同的圆形检测逻辑
@ -165,22 +172,45 @@ impl Circularity for LwPolyline {
poly_area /= 2.0; poly_area /= 2.0;
poly_area.abs() poly_area.abs()
}; };
let t_ratio = 4.0 * PI * poly_area / poly_perim.powf(2.0);
let t_ratio = 4.0 * PI * poly_area / poly_perim.powf(2.0);
Self::match_range().contains(&t_ratio) Self::match_range().contains(&t_ratio)
} }
fn is_circular_with_bulge(&self) -> bool {
// 检查是否有显著的bulge值
let has_bulge = self.vertices.iter().any(|v| v.bulge.abs() > 0.5);
if !has_bulge {
return false;
}
let all_quarter_arcs = self.vertices.iter()
.all(|v| (v.bulge.abs() - 1.0).abs() < 0.05);
if all_quarter_arcs {
// 检查是否闭合
if self.vertices.len() >= 3 {
let first = &self.vertices[0];
let last = &self.vertices[self.vertices.len() - 1];
let distance = ((last.x - first.x).powf(2.0) + (last.y - first.y).powf(2.0)).sqrt();
return distance < 0.1; // 起点终点接近
}
}
false
}
} }
// 实现Definition的方法 // 实现Definition的方法
// 最后返回文本的definition标签了
impl Definition { impl Definition {
// 创建新的 Definition 实例 // 创建新的 Definition 实例对应elmt的<definition>标签 传入参数:文件名, 步长, dxf图纸
pub fn new(name: impl Into<String>, spline_step: u32, drw: &Drawing) -> Self { pub fn new(name: impl Into<String>, spline_step: u32, drw: &Drawing) -> Self {
/*for st in drw.styles() { /*for st in drw.styles() {
dbg!(st); dbg!(st);
}*/ }*/
// 缩放处理 // 根据制图系统的类型缩放处理
let scale_factor = Self::scale_factor(drw.header.default_drawing_units); let scale_factor = Self::scale_factor(drw.header.default_drawing_units);
// 创建description // 创建description对应elmt的<description>标签,里面就是实际的画图元素标签
let description = { let description = {
let mut description: Description = (drw, spline_step).into(); let mut description: Description = (drw, spline_step).into();
description.scale(scale_factor, scale_factor); description.scale(scale_factor, scale_factor);
@ -530,7 +560,7 @@ pub struct ObjectsBuilder<'a> {
spline_step: u32, spline_step: u32,
blocks: &'a [&'a Block], blocks: &'a [&'a Block],
offset: Offset, offset: Offset,
scale_fact: ScaleFactor, scale_fact: ScaleFactor
} }
impl<'a> ObjectsBuilder<'a> { impl<'a> ObjectsBuilder<'a> {
@ -576,19 +606,54 @@ impl<'a> ObjectsBuilder<'a> {
return Err("Entity is not visible"); return Err("Entity is not visible");
} }
// 获取线型名称
let line_type_name: String = self.ent.common.line_type_name.clone();
// 通用样式修改函数根据line_type_name修改现有style中的line-style
let update_line_style = |style: &mut String| {
// 只有当line_type_name需要虚线样式时才进行修改提高性能
if line_type_name.contains("DASH") || line_type_name == "BORDURE" {
// 使用正则表达式替换line-style部分
if style.contains("line-style:") {
*style = style.replace(
&format!("line-style:{}",
if style.contains("line-style:normal") { "normal" }
else if style.contains("line-style:dashed") { "dashed" }
else { "normal" }
),
"line-style:dashed"
);
}
}
// 如果不是特殊线型,保持原有样式不变,避免不必要的操作
};
// 实体类型转换处理 // 实体类型转换处理
match &self.ent.specific { match &self.ent.specific {
// Circle → Ellipse :圆转换为椭圆 // Circle → Ellipse :圆转换为椭圆
EntityType::Circle(circle) => { EntityType::Circle(circle) => {
let mut ellipse: Ellipse = circle.into(); let mut ellipse: Ellipse = circle.into();
// 根据line_type_name更新线型样式
ellipse.update_line_style(&update_line_style);
ellipse.scale(self.scale_fact.x, self.scale_fact.y); ellipse.scale(self.scale_fact.x, self.scale_fact.y);
ellipse.x += self.offset.x; ellipse.x += self.offset.x;
ellipse.y -= self.offset.y; ellipse.y -= self.offset.y;
Ok(Objects::Ellipse(ellipse)) Ok(Objects::Ellipse(ellipse))
} }
EntityType::Line(line) => { EntityType::Line(line) => {
let mut line: Line = line.into(); // 获取颜色
let color = if let Some(aci_index) = self.ent.common.color.index() {
aci_to_hex_color(aci_index)
} else {
HexColor::from_u32(self.ent.common.color_24_bit as u32)
};
let mut line: Line = Line::from_line_with_color(line, color);
// 根据line_type_name更新线型样式
line.update_line_style(&update_line_style);
line.scale(self.scale_fact.x, self.scale_fact.y); line.scale(self.scale_fact.x, self.scale_fact.y);
@ -598,10 +663,23 @@ impl<'a> ObjectsBuilder<'a> {
line.x2 += self.offset.x; line.x2 += self.offset.x;
line.y2 -= self.offset.y; line.y2 -= self.offset.y;
println!("Line: x1: {}, y1: {}, x2: {}, y2: {}", line.x1, line.y1, line.x2, line.y2);
Ok(Objects::Line(line)) Ok(Objects::Line(line))
} }
EntityType::Arc(arc) => { EntityType::Arc(arc) => {
let mut arc: Arc = arc.into(); // 获取颜色
let color = if let Some(aci_index) = self.ent.common.color.index() {
aci_to_hex_color(aci_index)
} else {
HexColor::from_u32(self.ent.common.color_24_bit as u32)
};
let mut arc: Arc = Arc::from_arc_with_color(arc, color);
// 根据line_type_name更新线型样式
arc.update_line_style(&update_line_style);
arc.scale(self.scale_fact.x, self.scale_fact.y); arc.scale(self.scale_fact.x, self.scale_fact.y);
@ -613,6 +691,9 @@ impl<'a> ObjectsBuilder<'a> {
EntityType::Spline(spline) => { EntityType::Spline(spline) => {
let mut poly: Polygon = (spline, self.spline_step).into(); let mut poly: Polygon = (spline, self.spline_step).into();
// 根据line_type_name更新线型样式
poly.update_line_style(&update_line_style);
match poly.coordinates.len() { match poly.coordinates.len() {
0 | 1 => Err("Error removing empty Spline"), 0 | 1 => Err("Error removing empty Spline"),
//I'll need to improve my understanding of splines and the math here //I'll need to improve my understanding of splines and the math here
@ -641,7 +722,13 @@ impl<'a> ObjectsBuilder<'a> {
//how best to pass in the flag for dynamic text or not....should the flag also default to true? //how best to pass in the flag for dynamic text or not....should the flag also default to true?
let mut text: Text = ( let mut text: Text = (
text, text,
HexColor::from_u32(self.ent.common.color_24_bit as u32), {
if let Some(aci_index) = self.ent.common.color.index() {
aci_to_hex_color(aci_index)
} else {
HexColor::from_u32(self.ent.common.color_24_bit as u32)
}
},
) )
.into(); .into();
@ -653,7 +740,15 @@ impl<'a> ObjectsBuilder<'a> {
Objects::Text(text) Objects::Text(text)
} else { } else {
let mut dtext = DTextBuilder::from_text(text) let mut dtext = DTextBuilder::from_text(text)
.color(HexColor::from_u32(self.ent.common.color_24_bit as u32)) .color({
// 优先使用 ACI 颜色索引
if let Some(aci_index) = self.ent.common.color.index() {
aci_to_hex_color(aci_index)
} else {
// 回退到24位颜色
HexColor::from_u32(self.ent.common.color_24_bit as u32)
}
})
.build(); .build();
dtext.scale(self.scale_fact.x, self.scale_fact.y); dtext.scale(self.scale_fact.x, self.scale_fact.y);
@ -668,6 +763,9 @@ impl<'a> ObjectsBuilder<'a> {
EntityType::Ellipse(ellipse) => { EntityType::Ellipse(ellipse) => {
let mut ellipse: Ellipse = ellipse.into(); let mut ellipse: Ellipse = ellipse.into();
// 根据line_type_name更新线型样式
ellipse.update_line_style(&update_line_style);
ellipse.scale(self.scale_fact.x, self.scale_fact.y); ellipse.scale(self.scale_fact.x, self.scale_fact.y);
ellipse.x += self.offset.x; ellipse.x += self.offset.x;
ellipse.y -= self.offset.y; ellipse.y -= self.offset.y;
@ -709,6 +807,9 @@ impl<'a> ObjectsBuilder<'a> {
2 => { 2 => {
let mut line = Line::try_from(polyline)?; let mut line = Line::try_from(polyline)?;
// 根据line_type_name更新线型样式
line.update_line_style(&update_line_style);
line.scale(self.scale_fact.x, self.scale_fact.x); line.scale(self.scale_fact.x, self.scale_fact.x);
line.x1 += self.offset.x; line.x1 += self.offset.x;
@ -721,6 +822,9 @@ impl<'a> ObjectsBuilder<'a> {
} }
_ => { _ => {
if let Ok(mut ellipse) = Ellipse::try_from(polyline) { if let Ok(mut ellipse) = Ellipse::try_from(polyline) {
// 根据line_type_name更新线型样式
ellipse.update_line_style(&update_line_style);
ellipse.scale(self.scale_fact.x, self.scale_fact.y); ellipse.scale(self.scale_fact.x, self.scale_fact.y);
ellipse.x += self.offset.x; ellipse.x += self.offset.x;
@ -730,6 +834,9 @@ impl<'a> ObjectsBuilder<'a> {
} else { } else {
let mut poly: Polygon = polyline.into(); let mut poly: Polygon = polyline.into();
// 根据line_type_name更新线型样式
poly.update_line_style(&update_line_style);
poly.scale(self.scale_fact.x, self.scale_fact.y); poly.scale(self.scale_fact.x, self.scale_fact.y);
for cord in &mut poly.coordinates { for cord in &mut poly.coordinates {
@ -741,10 +848,36 @@ impl<'a> ObjectsBuilder<'a> {
} }
} }
}, },
EntityType::LwPolyline(lwpolyline) => match lwpolyline.vertices.len() { EntityType::LwPolyline(lwpolyline) => {
// 获取颜色
let color = if let Some(aci_index) = self.ent.common.color.index() {
aci_to_hex_color(aci_index)
} else {
HexColor::from_u32(self.ent.common.color_24_bit as u32)
};
match lwpolyline.vertices.len() {
0 | 1 => Err("Error empty LwPolyline"), 0 | 1 => Err("Error empty LwPolyline"),
2 => { 2 => {
let mut line = Line::try_from(lwpolyline)?; if lwpolyline.vertices[0].bulge != 0.0 {
match Arc::try_from_lwpolyline_with_color(lwpolyline, color) {
Ok(mut arc) => {
arc.update_line_style(&update_line_style);
arc.scale(self.scale_fact.x, self.scale_fact.y);
arc.x += self.offset.x;
arc.y -= self.offset.y;
return Ok(Objects::Arc(arc));
}
Err(_) => {
// Arc转换失败继续执行下面的Line转换
}
}
}
let mut line = Line::try_from_lwpolyline_with_color(lwpolyline, color)?;
// 根据line_type_name更新线型样式
line.update_line_style(&update_line_style);
line.scale(self.scale_fact.x, self.scale_fact.y); line.scale(self.scale_fact.x, self.scale_fact.y);
@ -757,15 +890,44 @@ impl<'a> ObjectsBuilder<'a> {
Ok(Objects::Line(line)) Ok(Objects::Line(line))
} }
_ => { _ => {
if let Ok(mut ellipse) = Ellipse::try_from(lwpolyline) { if let Ok(mut ellipse) = Ellipse::try_from_lwpolyline_with_color(lwpolyline, color) {
// 根据line_type_name更新线型样式
ellipse.update_line_style(&update_line_style);
ellipse.scale(self.scale_fact.x, self.scale_fact.y); ellipse.scale(self.scale_fact.x, self.scale_fact.y);
ellipse.x += self.offset.x; ellipse.x += self.offset.x;
ellipse.y -= self.offset.y; ellipse.y -= self.offset.y;
Ok(Objects::Ellipse(ellipse)) Ok(Objects::Ellipse(ellipse))
} else if polygon::is_rounded_rectangle(lwpolyline) {
// 拆分圆角四边形为圆弧和直线段
let mut decomposed_objects = polygon::decompose_rounded_rectangle_with_color(lwpolyline, color);
// 对每个对象应用缩放和偏移
for obj in &mut decomposed_objects {
obj.scale(self.scale_fact.x, self.scale_fact.y);
match obj {
Objects::Arc(ref mut arc) => {
arc.x += self.offset.x;
arc.y -= self.offset.y;
}
Objects::Line(ref mut line) => {
line.x1 += self.offset.x;
line.y1 -= self.offset.y;
line.x2 += self.offset.x;
line.y2 -= self.offset.y;
}
_ => {}
}
}
Ok(Objects::Group(decomposed_objects))
} else { } else {
let mut poly: Polygon = lwpolyline.into(); let mut poly: Polygon = Polygon::from_lwpolyline_with_color(lwpolyline, color);
// 根据line_type_name更新线型样式
poly.update_line_style(&update_line_style);
poly.scale(self.scale_fact.x, self.scale_fact.y); poly.scale(self.scale_fact.x, self.scale_fact.y);
@ -777,6 +939,7 @@ impl<'a> ObjectsBuilder<'a> {
Ok(Objects::Polygon(poly)) Ok(Objects::Polygon(poly))
} }
} }
}
}, },
EntityType::Solid(solid) => { EntityType::Solid(solid) => {
let mut poly: Polygon = solid.into(); let mut poly: Polygon = solid.into();
@ -849,7 +1012,15 @@ impl<'a> ObjectsBuilder<'a> {
EntityType::AttributeDefinition(attrib) => Ok({ EntityType::AttributeDefinition(attrib) => Ok({
//need to look up the proper way to get the color for the Attrib //need to look up the proper way to get the color for the Attrib
let mut dtext = DTextBuilder::from_attrib(attrib) let mut dtext = DTextBuilder::from_attrib(attrib)
.color(HexColor::from_u32(self.ent.common.color_24_bit as u32)) .color({
// 优先使用 ACI 颜色索引
if let Some(aci_index) = self.ent.common.color.index() {
aci_to_hex_color(aci_index)
} else {
// 回退到24位颜色
HexColor::from_u32(self.ent.common.color_24_bit as u32)
}
})
.build(); .build();
dtext.scale(self.scale_fact.x, self.scale_fact.y); dtext.scale(self.scale_fact.x, self.scale_fact.y);
@ -1205,6 +1376,7 @@ impl From<HorizontalTextJustification> for HAlignment {
match value { match value {
HorizontalTextJustification::Left => HAlignment::Left, HorizontalTextJustification::Left => HAlignment::Left,
HorizontalTextJustification::Center => HAlignment::Center, HorizontalTextJustification::Center => HAlignment::Center,
HorizontalTextJustification::Middle => HAlignment::Center,
HorizontalTextJustification::Right => HAlignment::Right, HorizontalTextJustification::Right => HAlignment::Right,
//TODO: Handling the Aligned Middle and Fit alignments are a bit more complicated //TODO: Handling the Aligned Middle and Fit alignments are a bit more complicated
@ -1539,3 +1711,153 @@ enum TextEntity<'a> {
MText(&'a dxf::entities::MText), MText(&'a dxf::entities::MText),
Attrib(&'a AttributeDefinition), Attrib(&'a AttributeDefinition),
} }
// AutoCAD Color Index (ACI) 到 RGB 转换函数
fn aci_to_rgb(aci: u8) -> (u8, u8, u8) {
match aci {
1 => (255, 0, 0), // 红色
2 => (255, 255, 0), // 黄色
3 => (0, 255, 0), // 绿色
4 => (0, 255, 255), // 青色
5 => (0, 0, 255), // 蓝色
6 => (255, 0, 255), // 洋红色
// 7 => (255, 255, 255), // 白色
8 => (128, 128, 128), // 深灰色
9 => (192, 192, 192), // 浅灰色
94 => (0, 129, 0), // 深绿色
15 => (129, 86, 86), // 深棕色
// 更多标准颜色可以根据需要添加
_ => (0, 0, 0), // 默认黑色
}
}
// ACI 转换为 HexColor
fn aci_to_hex_color(aci: u8) -> HexColor {
let (r, g, b) = aci_to_rgb(aci);
HexColor::rgb(r, g, b)
}
/// 从PCL格式字符串中提取文本内容
///
/// 解析Printer Command Language (PCL)格式的字符串,提取其中的文本内容。
/// 支持格式如:"\pxt1;{\T1.001;传感器S1}" -> "传感器S1"
/// 以及Unicode转义格式"{\T1.00100;\U+4F20\U+611F\U+5668S2}" -> "传感器S2"
///
/// # 参数
/// * `pcl_string` - PCL格式的字符串
///
/// # 返回值
/// 提取出的文本内容,如果解析失败则返回原字符串
pub fn extract_text_from_pcl(pcl_string: &str) -> String {
// 移除开头和结尾的空白字符
let trimmed = pcl_string.trim();
// 处理直接以大括号开始的格式:{\T1.00100;\U+4F20\U+611F\U+5668S2}
if trimmed.starts_with('{') && trimmed.ends_with('}') {
let content = &trimmed[1..trimmed.len() - 1];
// 查找\T命令
if let Some(t_pos) = content.find("\\T") {
// 查找\T命令后的分号
let t_content = &content[t_pos..];
if let Some(t_semicolon_pos) = t_content.find(';') {
let text_content = &t_content[t_semicolon_pos + 1..];
return decode_unicode_escapes(text_content);
}
}
// 如果没有找到\T命令返回大括号内的全部内容并解码Unicode
return decode_unicode_escapes(content);
}
// 检查是否以\pxt开头
if !trimmed.starts_with("\\pxt") {
return pcl_string.to_string();
}
// 查找第一个分号,这标志着\pxt命令的结束
if let Some(semicolon_pos) = trimmed.find(';') {
let remaining = &trimmed[semicolon_pos + 1..];
// 检查是否有大括号包围的内容
if remaining.starts_with('{') && remaining.ends_with('}') {
let content = &remaining[1..remaining.len() - 1];
// 查找\T命令
if let Some(t_pos) = content.find("\\T") {
// 查找\T命令后的分号
let t_content = &content[t_pos..];
if let Some(t_semicolon_pos) = t_content.find(';') {
let text_content = &t_content[t_semicolon_pos + 1..];
return decode_unicode_escapes(text_content);
}
}
// 如果没有找到\T命令返回大括号内的全部内容并解码Unicode
return decode_unicode_escapes(content);
}
}
// 如果解析失败,返回原字符串
pcl_string.to_string()
}
/// 解码Unicode转义序列如\U+4F20转换为对应的Unicode字符
///
/// # 参数
/// * `text` - 包含Unicode转义序列的文本
///
/// # 返回值
/// 解码后的文本
fn decode_unicode_escapes(text: &str) -> String {
let mut result = String::new();
let mut chars = text.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\\' {
// 检查是否是Unicode转义序列 \U+XXXX
if chars.peek() == Some(&'U') {
chars.next(); // 消费 'U'
if chars.peek() == Some(&'+') {
chars.next(); // 消费 '+'
// 收集十六进制数字
let mut hex_digits = String::new();
while let Some(&next_ch) = chars.peek() {
if next_ch.is_ascii_hexdigit() {
hex_digits.push(chars.next().unwrap());
} else {
break;
}
}
// 尝试解析十六进制数字为Unicode码点
if let Ok(code_point) = u32::from_str_radix(&hex_digits, 16) {
if let Some(unicode_char) = char::from_u32(code_point) {
result.push(unicode_char);
continue;
}
}
// 如果解析失败,保留原始字符
result.push('\\');
result.push('U');
result.push('+');
result.push_str(&hex_digits);
} else {
// 不是 \U+ 格式,保留原始字符
result.push('\\');
result.push('U');
}
} else {
// 不是Unicode转义保留反斜杠
result.push('\\');
}
} else {
result.push(ch);
}
}
result
}

@ -1,5 +1,6 @@
use super::{two_dec, ScaleEntity}; use super::{two_dec, ScaleEntity};
use dxf::entities::{LwPolyline, Polyline, Solid, Spline}; use dxf::entities::{LwPolyline, Polyline, Solid, Spline};
use hex_color::HexColor;
use simple_xml_builder::XMLElement; use simple_xml_builder::XMLElement;
use std::ops::{Add, Mul}; use std::ops::{Add, Mul};
@ -79,6 +80,13 @@ impl From<&Polyline> for Polygon {
impl From<&LwPolyline> for Polygon { impl From<&LwPolyline> for Polygon {
fn from(poly: &LwPolyline) -> Self { fn from(poly: &LwPolyline) -> Self {
Self::from_lwpolyline_with_color(poly, HexColor::BLACK)
}
}
impl Polygon {
/// 从DXF LwPolyline实体创建Polygon支持自定义颜色
pub fn from_lwpolyline_with_color(poly: &LwPolyline, color: HexColor) -> Self {
Polygon { Polygon {
coordinates: poly coordinates: poly
.vertices .vertices
@ -93,11 +101,10 @@ impl From<&LwPolyline> for Polygon {
//reasons...I'm trying to think if there is a time we might want to turn it on? //reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false, antialias: false,
style: if poly.thickness > 0.1 { style: if poly.thickness > 0.1 {
"line-style:normal;line-weight:normal;filling:none;color:black" format!("line-style:normal;line-weight:normal;filling:none;color:{}", color.display_rgb())
} else { } else {
"line-style:normal;line-weight:thin;filling:none;color:black" format!("line-style:normal;line-weight:thin;filling:none;color:{}", color.display_rgb())
} },
.into(),
} }
} }
} }
@ -185,9 +192,9 @@ impl From<&Solid> for Polygon {
//reasons...I'm trying to think if there is a time we might want to turn it on? //reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false, antialias: false,
style: if solid.thickness > 0.5 { style: if solid.thickness > 0.5 {
"line-style:normal;line-weight:normal;filling:none;color:black" "line-style:normal;line-weight:normal;filling:black;color:black"
} else { } else {
"line-style:normal;line-weight:thin;filling:none;color:black" "line-style:normal;line-weight:thin;filling:black;color:black"
} }
.into(), .into(),
} }
@ -214,6 +221,15 @@ impl From<&Polygon> for XMLElement {
} }
} }
impl Polygon {
pub fn update_line_style<F>(&mut self, update_fn: F)
where
F: FnOnce(&mut String),
{
update_fn(&mut self.style);
}
}
impl ScaleEntity for Polygon { impl ScaleEntity for Polygon {
fn scale(&mut self, fact_x: f64, fact_y: f64) { fn scale(&mut self, fact_x: f64, fact_y: f64) {
self.coordinates.iter_mut().for_each(|coord| { self.coordinates.iter_mut().for_each(|coord| {
@ -280,3 +296,176 @@ impl ScaleEntity for Polygon {
} }
} }
} }
/// 判断LwPolyline是否为带圆角的四边形
/// 条件恰好8个顶点闭合每个顶点的bulge值要么为0直线段要么非0圆弧段
pub fn is_rounded_rectangle(lwpolyline: &LwPolyline) -> bool {
// 检查顶点数量
if lwpolyline.vertices.len() != 8 {
return false;
}
// 检查每个顶点的bulge值确保形成合理的圆角四边形
// 圆角四边形应该有交替的直线段和圆弧段或者4个圆角
let bulge_count = lwpolyline.vertices.iter()
.filter(|v| v.bulge.abs() > 0.0)
.count();
// 4个圆角
bulge_count == 4
}
/// 将带圆角的四边形拆分为圆弧和直线段
pub fn decompose_rounded_rectangle(lwpolyline: &LwPolyline) -> Vec<super::Objects> {
decompose_rounded_rectangle_with_color(lwpolyline, HexColor::BLACK)
}
pub fn decompose_rounded_rectangle_with_color(lwpolyline: &LwPolyline, color: HexColor) -> Vec<super::Objects> {
let mut objects = Vec::new();
let vertices = &lwpolyline.vertices;
for i in 0..vertices.len() {
let current = &vertices[i];
let next = &vertices[(i + 1) % vertices.len()];
if current.bulge.abs() > 1e-10 {
// 创建圆弧段
if let Some(arc) = create_arc_from_bulge_with_color(current.x, current.y, next.x, next.y, current.bulge, color) {
objects.push(super::Objects::Arc(arc));
}
} else {
// 创建直线段
let style = if lwpolyline.thickness > 0.5 {
format!("line-style:normal;line-weight:normal;filling:none;color:color:{}", color.display_rgb())
} else {
format!("line-style:normal;line-weight:thin;filling:none;color:color:{}", color.display_rgb())
};
let line = super::Line::new(
current.x,
-current.y, // Y轴翻转
next.x,
-next.y, // Y轴翻转
style,
);
objects.push(super::Objects::Line(line));
}
}
objects
}
/// 根据bulge值创建圆弧
fn create_arc_from_bulge(start_x: f64, start_y: f64, end_x: f64, end_y: f64, bulge: f64) -> Option<super::Arc> {
create_arc_from_bulge_with_color(start_x, start_y, end_x, end_y, bulge, HexColor::BLACK)
}
fn create_arc_from_bulge_with_color(start_x: f64, start_y: f64, end_x: f64, end_y: f64, bulge: f64, color: HexColor) -> Option<super::Arc> {
if bulge.abs() <= 1e-10 {
return None;
}
// 计算弦长和中点
let chord_length = ((end_x - start_x).powi(2) + (end_y - start_y).powi(2)).sqrt();
let mid_x = (start_x + end_x) / 2.0;
let mid_y = (start_y + end_y) / 2.0;
// 根据bulge值计算圆弧参数
// bulge = tan(angle/4)其中angle是圆弧的包含角
let included_angle = 4.0 * bulge.atan(); // 圆弧包含角(弧度)
// 计算半径
let radius: f64 = chord_length / (2.0 * (included_angle / 2.0).sin());
// 计算圆心位置
// 弦的方向向量
let chord_dx = end_x - start_x;
let chord_dy = end_y - start_y;
// 弦的垂直方向向左旋转90度
let perp_dx = -chord_dy;
let perp_dy = chord_dx;
let perp_length: f64 = (perp_dx * perp_dx + perp_dy * perp_dy).sqrt();
// 标准化垂直向量
let unit_perp_x = perp_dx / perp_length;
let unit_perp_y = perp_dy / perp_length;
// 计算圆心到中点的距离
let center_distance: f64 = radius * (included_angle / 2.0).cos();
// 根据bulge的符号确定圆心位置
let center_x = if bulge > 0.0 {
mid_x + center_distance * unit_perp_x
} else {
mid_x - center_distance * unit_perp_x
};
let center_y = if bulge > 0.0 {
mid_y + center_distance * unit_perp_y
} else {
mid_y - center_distance * unit_perp_y
};
// 计算起始角度和结束角度
let start_angle = (start_y - center_y).atan2(start_x - center_x);
let end_angle = (end_y - center_y).atan2(end_x - center_x);
// 将弧度转换为度数
let mut start_angle_deg: f64 = start_angle.to_degrees();
let mut end_angle_deg: f64 = end_angle.to_degrees();
// 标准化角度到0-360度范围
if start_angle_deg < 0.0 {
start_angle_deg += 360.0;
}
if end_angle_deg < 0.0 {
end_angle_deg += 360.0;
}
// 计算角度跨度
let arc_angle: f64 = included_angle.to_degrees().abs();
// // 计算圆弧参数
// let start_x = start_vertex.x;
// let start_y = -start_vertex.y; // Y轴翻转
// let end_x = end_vertex.x;
// let end_y = -end_vertex.y; // Y轴翻转
// // 弦长
// let chord_length = ((end_x - start_x).powi(2) + (end_y - start_y).powi(2)).sqrt();
// if chord_length < 1e-10 {
// return None;
// }
// // 根据bulge计算圆弧的角度
// let angle = 4.0 * bulge.atan();
// // 计算圆弧的半径
// let radius = chord_length / (2.0 * (angle / 2.0).sin());
// // 计算中点
// let mid_x = (start_x + end_x) / 2.0;
// let mid_y = (start_y + end_y) / 2.0;
// // 计算弦角
// let chord_angle = (end_y - start_y).atan2(end_x - start_x);
// let center_offset = radius * (angle / 2.0).cos();
// let center_x = mid_x - center_offset * (chord_angle + std::f64::consts::PI / 2.0).sin();
// let center_y = mid_y + center_offset * (chord_angle + std::f64::consts::PI / 2.0).cos();
// // 计算起始角度和角度跨度
// let start_angle = (start_y - center_y).atan2(start_x - center_x).to_degrees();
// let angle_span = angle.to_degrees().abs();
let style = format!("line-style:normal;line-weight:thin;filling:none;color:{}", color.display_rgb());
Some(super::Arc::new(
center_x-radius,
-center_y-radius,
radius.abs() * 2.0,
radius.abs() * 2.0,
start_angle_deg,
arc_angle,
style,
))
}
Loading…
Cancel
Save