|
|
|
|
@ -718,6 +718,64 @@ impl Objects {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 判断 LWPOLYLINE 的有效线段中是否包含 bulge 圆弧。
|
|
|
|
|
/// 开放折线的最后一个顶点没有“下一顶点”,其 bulge 不参与绘制;
|
|
|
|
|
/// 闭合折线则还要检查最后一个顶点到第一个顶点的闭合段。
|
|
|
|
|
fn lwpolyline_has_bulged_segment(polyline: &LwPolyline) -> bool {
|
|
|
|
|
let segment_count = if polyline.is_closed() {
|
|
|
|
|
polyline.vertices.len()
|
|
|
|
|
} else {
|
|
|
|
|
polyline.vertices.len().saturating_sub(1)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
polyline
|
|
|
|
|
.vertices
|
|
|
|
|
.iter()
|
|
|
|
|
.take(segment_count)
|
|
|
|
|
.any(|vertex| vertex.bulge.abs() > 1e-10)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 将一条同时包含直线和 bulge 圆弧的 LWPOLYLINE 按段拆开。
|
|
|
|
|
/// 每个顶点的 bulge 描述“当前顶点 -> 下一顶点”:非零生成 Arc,零值生成 Line。
|
|
|
|
|
fn decompose_lwpolyline_segments(
|
|
|
|
|
polyline: &LwPolyline,
|
|
|
|
|
color: HexColor,
|
|
|
|
|
) -> Result<Vec<Objects>, &'static str> {
|
|
|
|
|
let vertex_count = polyline.vertices.len();
|
|
|
|
|
if vertex_count < 2 {
|
|
|
|
|
return Err("Error empty LwPolyline");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let segment_count = if polyline.is_closed() {
|
|
|
|
|
vertex_count
|
|
|
|
|
} else {
|
|
|
|
|
vertex_count - 1
|
|
|
|
|
};
|
|
|
|
|
let mut objects = Vec::with_capacity(segment_count);
|
|
|
|
|
|
|
|
|
|
for index in 0..segment_count {
|
|
|
|
|
// 复用现有的两顶点 Line/Arc 转换器。临时段必须设为开放,
|
|
|
|
|
// 防止两顶点闭合折线被转换器再次补上一条回程线段。
|
|
|
|
|
let mut segment = polyline.clone();
|
|
|
|
|
segment.flags = 0;
|
|
|
|
|
segment.vertices = vec![
|
|
|
|
|
polyline.vertices[index],
|
|
|
|
|
polyline.vertices[(index + 1) % vertex_count],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if segment.vertices[0].bulge.abs() > 1e-10 {
|
|
|
|
|
let arc = Arc::try_from_lwpolyline_with_color(&segment, color)
|
|
|
|
|
.map_err(|_| "Error converting LwPolyline bulge segment")?;
|
|
|
|
|
objects.push(Objects::Arc(arc));
|
|
|
|
|
} else {
|
|
|
|
|
let line = Line::try_from_lwpolyline_with_color(&segment, color)?;
|
|
|
|
|
objects.push(Objects::Line(line));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(objects)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn rotate_objects_around(objects: &mut [Objects], pivot_x: f64, pivot_y: f64, angle_deg: f64) {
|
|
|
|
|
if angle_deg.abs() <= f64::EPSILON {
|
|
|
|
|
return;
|
|
|
|
|
@ -1304,27 +1362,28 @@ impl<'a> ObjectsBuilder<'a> {
|
|
|
|
|
ellipse.y -= self.offset.y;
|
|
|
|
|
|
|
|
|
|
Ok(Objects::Ellipse(ellipse))
|
|
|
|
|
} else if polygon::is_rounded_rectangle(lwpolyline) {
|
|
|
|
|
// 拆分圆角四边形为圆弧和直线段
|
|
|
|
|
} else if lwpolyline_has_bulged_segment(lwpolyline) {
|
|
|
|
|
// 不能把混合折线整体降级为 Polygon,否则 bulge 信息会被丢弃,
|
|
|
|
|
// 原图中的圆弧就会变成连接端点的直线。
|
|
|
|
|
let mut decomposed_objects =
|
|
|
|
|
polygon::decompose_rounded_rectangle_with_color(lwpolyline, color);
|
|
|
|
|
decompose_lwpolyline_segments(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::Arc(arc) => {
|
|
|
|
|
arc.update_line_style(&update_line_style);
|
|
|
|
|
if let Some(w) = lw {
|
|
|
|
|
arc.update_line_weight(w);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
Objects::Line(line) => {
|
|
|
|
|
line.update_line_style(&update_line_style);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
obj.scale(self.scale_fact.x, self.scale_fact.y);
|
|
|
|
|
obj.translate(self.offset.x, -self.offset.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Objects::Group(decomposed_objects))
|
|
|
|
|
@ -2593,11 +2652,11 @@ pub(crate) fn strip_mtext_control_sequences(input: &str) -> String {
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::{
|
|
|
|
|
BlockInstance, BlockMetadata, Definition, Description, Line, Objects, ObjectsBuilder,
|
|
|
|
|
ScaleEntity,
|
|
|
|
|
xml_elements_for_object, BlockInstance, BlockMetadata, Definition, Description, Line,
|
|
|
|
|
Objects, ObjectsBuilder, ScaleEntity,
|
|
|
|
|
};
|
|
|
|
|
use dxf::entities::{Attribute, Entity, EntityType, Insert};
|
|
|
|
|
use dxf::{Drawing, Point};
|
|
|
|
|
use dxf::entities::{Attribute, Entity, EntityType, Insert, LwPolyline};
|
|
|
|
|
use dxf::{Drawing, LwPolylineVertex, Point};
|
|
|
|
|
|
|
|
|
|
fn connector_insert(rotation: f64) -> Insert {
|
|
|
|
|
let mut insert = Insert {
|
|
|
|
|
@ -2651,6 +2710,49 @@ mod tests {
|
|
|
|
|
assert!(xml.contains("<connection_point tag=\"N:0-C:0\""));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn mixed_lwpolyline_keeps_line_and_negative_bulge_arc() {
|
|
|
|
|
let polyline = LwPolyline {
|
|
|
|
|
constant_width: 0.35,
|
|
|
|
|
vertices: vec![
|
|
|
|
|
LwPolylineVertex {
|
|
|
|
|
x: 0.0,
|
|
|
|
|
y: 0.0,
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
LwPolylineVertex {
|
|
|
|
|
x: 0.0,
|
|
|
|
|
y: 1.666_666_7,
|
|
|
|
|
bulge: -1.0,
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
LwPolylineVertex {
|
|
|
|
|
x: 5.0,
|
|
|
|
|
y: 1.666_666_7,
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
let entity = Entity::new(EntityType::LwPolyline(polyline));
|
|
|
|
|
let object = ObjectsBuilder::new(&entity, 20)
|
|
|
|
|
.build()
|
|
|
|
|
.expect("mixed LWPOLYLINE should be supported");
|
|
|
|
|
let xml = xml_elements_for_object(&object, None)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|element| element.to_string())
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join("");
|
|
|
|
|
|
|
|
|
|
assert_eq!(1, xml.matches("<line ").count(), "unexpected XML: {xml}");
|
|
|
|
|
assert_eq!(1, xml.matches("<arc ").count(), "unexpected XML: {xml}");
|
|
|
|
|
assert!(!xml.contains("<polygon "), "unexpected XML: {xml}");
|
|
|
|
|
assert!(xml.contains("width=\"5\""), "unexpected XML: {xml}");
|
|
|
|
|
assert!(xml.contains("height=\"5\""), "unexpected XML: {xml}");
|
|
|
|
|
assert!(xml.contains("start=\"0\""), "unexpected XML: {xml}");
|
|
|
|
|
assert!(xml.contains("angle=\"180\""), "unexpected XML: {xml}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn description_bounds_use_outer_edges_for_single_group() {
|
|
|
|
|
let description = Description {
|
|
|
|
|
|