|
|
use dxf::entities::{AttributeDefinition, Entity, EntityType};
|
|
|
use dxf::entities::{LwPolyline, Polyline};
|
|
|
use dxf::enums::{AttachmentPoint, HorizontalTextJustification, Units, VerticalTextJustification};
|
|
|
use dxf::{Block, Drawing};
|
|
|
use dynamictext::DTextBuilder;
|
|
|
use hex_color::HexColor;
|
|
|
use itertools::Itertools;
|
|
|
use simple_xml_builder::XMLElement;
|
|
|
use std::convert::TryFrom;
|
|
|
use std::f64::consts::PI;
|
|
|
use std::fmt::Display;
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
use tracing::{error, info, span, trace, Level};
|
|
|
|
|
|
pub mod arc;
|
|
|
pub use arc::Arc;
|
|
|
|
|
|
pub mod line;
|
|
|
pub use line::{Leader, Line};
|
|
|
|
|
|
pub mod text;
|
|
|
pub use text::Text;
|
|
|
|
|
|
pub mod dynamictext;
|
|
|
pub use dynamictext::DynamicText;
|
|
|
|
|
|
pub mod polygon;
|
|
|
pub use polygon::Polygon;
|
|
|
|
|
|
pub mod ellipse;
|
|
|
pub use ellipse::Ellipse;
|
|
|
|
|
|
// 在 DXF 图纸中根据名称查找特定的块(Block)
|
|
|
fn find_block<'a>(drw: &'a Drawing, name: &str) -> Option<&'a Block> {
|
|
|
//this is ugly there has to be a cleaner way to filter this....but for my first attempt at pulling the
|
|
|
//blocks out of the drawing it works.
|
|
|
//I mean would this ever return more than 1? I would assume block names have to be unique?
|
|
|
//but maybe not, the blocks have a handle, which is a u64. There is a get by handle function
|
|
|
//but not a get by name function....maybe the handle is what is unique and there can be duplicate names?
|
|
|
//a quick glance through the dxf code it looks like the handle might be given to the library user when inserting
|
|
|
//an entity? So I don't think there is any easy way to get the handle
|
|
|
drw.blocks().filter(|bl| bl.name == name).take(1).next()
|
|
|
}
|
|
|
|
|
|
// 枚举作用 :表示两种可能的类型之一
|
|
|
#[derive(Debug)]
|
|
|
enum Either<L, R> {
|
|
|
Left(L),
|
|
|
Right(R),
|
|
|
}
|
|
|
|
|
|
// Definition结构体,表示 ELMT 文件的定义,包含元素的尺寸、热点、版本、UUID 等信息
|
|
|
#[derive(Debug)]
|
|
|
pub struct Definition {
|
|
|
r#type: ItemType,
|
|
|
width: i64,
|
|
|
height: i64,
|
|
|
hotspot_x: i64,
|
|
|
hotspot_y: i64,
|
|
|
version: String,
|
|
|
link_type: LinkType,
|
|
|
uuid: ElmtUuid,
|
|
|
names: Names,
|
|
|
element_infos: Option<ElemInfos>,
|
|
|
informations: &'static str,
|
|
|
description: Description,
|
|
|
//counts
|
|
|
}
|
|
|
|
|
|
//Since the ScaleEntity trait was added to all the objects/elements
|
|
|
//and I need to add the get bounds to all it probably makes sense to have
|
|
|
|
|
|
//them all within the same trait instead of multiple traits, as a collective
|
|
|
//set of functions needed by the objects...but I should probably come up with
|
|
|
//a better trait name then. For now I'll leave it and just get the code working
|
|
|
// 特征定义,定义了一组方法,用于缩放实体、获取实体的边界和判断实体是否为圆形
|
|
|
trait ScaleEntity {
|
|
|
fn scale(&mut self, fact_x: f64, fact_y: f64);
|
|
|
|
|
|
fn left_bound(&self) -> f64;
|
|
|
fn right_bound(&self) -> f64;
|
|
|
|
|
|
fn top_bound(&self) -> f64;
|
|
|
fn bot_bound(&self) -> f64;
|
|
|
}
|
|
|
|
|
|
// 用于判断多边形是否接近圆形的特征,有点类似函数声明
|
|
|
trait Circularity {
|
|
|
// 判断图形是否为圆形
|
|
|
fn is_circular(&self) -> bool;
|
|
|
|
|
|
// 返回圆形判断的容差范围 (0.98..=1.02),即允许2%的误差
|
|
|
fn match_range() -> std::ops::RangeInclusive<f64> {
|
|
|
//this boundary of 2% has been chosen arbitrarily, I might adjust this later
|
|
|
//I know in one of my sample files, I'm getting a value of 0.99....
|
|
|
//since Associated Constants in a trait can't have a default value
|
|
|
//I'm using this function that defaults to a constant range of 2%
|
|
|
//Then I could also easily overwrite it if I wanted to change the tolerance
|
|
|
//for a specific type
|
|
|
0.98..=1.02
|
|
|
}
|
|
|
|
|
|
// 考虑bugle圆形判断
|
|
|
fn is_circular_with_bulge(&self) -> bool;
|
|
|
}
|
|
|
|
|
|
// 为 Polyline 类型实现圆形检测,有点类似函数定义
|
|
|
impl Circularity for Polyline {
|
|
|
fn is_circular(&self) -> bool {
|
|
|
let poly_perim: f64 = {
|
|
|
let tmp_pts: Vec<dxf::Point> = self.vertices().map(|v| v.clone().location).collect();
|
|
|
let len = tmp_pts.len();
|
|
|
tmp_pts
|
|
|
.into_iter()
|
|
|
.circular_tuple_windows()
|
|
|
.map(|(fst, sec)| ((fst.x - sec.x).powf(2.0) + (fst.y - sec.y).powf(2.0)).sqrt())
|
|
|
.take(len)
|
|
|
.sum()
|
|
|
};
|
|
|
|
|
|
let poly_area = {
|
|
|
//because instead of being able to access the Vec like in LwPolyline, vertices() returns
|
|
|
//an iter of dxf Vertex's which don't implement clone so I can't use circular_tuple_windows
|
|
|
//there is probably a cleaner way of iterating over this, but it's late, I'm getting tired
|
|
|
//and just want to see if this basic idea will work on my sample file, or see if I'm chasing
|
|
|
//up the wrong tree.
|
|
|
let tmp_pts: Vec<dxf::Point> = self.vertices().map(|v| v.clone().location).collect();
|
|
|
let len = tmp_pts.len();
|
|
|
let mut poly_area: f64 = tmp_pts
|
|
|
.into_iter()
|
|
|
.circular_tuple_windows()
|
|
|
.map(|(fst, sec)| (fst.x * sec.y) - (fst.y * sec.x))
|
|
|
.take(len)
|
|
|
.sum();
|
|
|
poly_area /= 2.0;
|
|
|
poly_area.abs()
|
|
|
};
|
|
|
let t_ratio = 4.0 * PI * poly_area / poly_perim.powf(2.0);
|
|
|
|
|
|
Self::match_range().contains(&t_ratio)
|
|
|
}
|
|
|
|
|
|
fn is_circular_with_bulge(&self) -> bool{
|
|
|
false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 为 LwPolyline 类型实现相同的圆形检测逻辑
|
|
|
impl Circularity for LwPolyline {
|
|
|
fn is_circular(&self) -> bool {
|
|
|
let poly_perim: f64 = self
|
|
|
.vertices
|
|
|
.iter()
|
|
|
.circular_tuple_windows()
|
|
|
.map(|(fst, sec)| {
|
|
|
((fst.x - sec.x).powf(2.0) + (fst.y - sec.y).powf(2.0))
|
|
|
.abs()
|
|
|
.sqrt()
|
|
|
})
|
|
|
.take(self.vertices.len())
|
|
|
.sum();
|
|
|
|
|
|
let poly_area = {
|
|
|
let mut poly_area: f64 = self
|
|
|
.vertices
|
|
|
.iter()
|
|
|
.circular_tuple_windows()
|
|
|
.map(|(fst, sec)| (fst.x * sec.y) - (fst.y * sec.x))
|
|
|
.take(self.vertices.len())
|
|
|
.sum();
|
|
|
poly_area /= 2.0;
|
|
|
poly_area.abs()
|
|
|
};
|
|
|
|
|
|
let t_ratio = 4.0 * PI * poly_area / poly_perim.powf(2.0);
|
|
|
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标签
|
|
|
impl Definition {
|
|
|
// 创建新的 Definition 实例,对应elmt的<definition>标签 传入参数:文件名, 步长, dxf图纸
|
|
|
pub fn new(name: impl Into<String>, spline_step: u32, drw: &Drawing) -> Self {
|
|
|
/*for st in drw.styles() {
|
|
|
dbg!(st);
|
|
|
}*/
|
|
|
// 根据制图系统的类型缩放处理
|
|
|
let scale_factor = Self::scale_factor(drw.header.default_drawing_units);
|
|
|
// 创建description,对应elmt的<description>标签,里面就是实际的画图元素标签
|
|
|
let description = {
|
|
|
let mut description: Description = (drw, spline_step).into();
|
|
|
description.scale(scale_factor, scale_factor);
|
|
|
description
|
|
|
};
|
|
|
|
|
|
// 计算图纸的宽度,x热点坐标
|
|
|
//The below calculation for width and hotspot_x are taken from the qet source code
|
|
|
let (width, hotspot_x) = {
|
|
|
let tmp_width = description.right_bound() - description.left_bound();
|
|
|
let int_width = tmp_width.round() as i64;
|
|
|
let upwidth = ((int_width / 10) * 10) + 10;
|
|
|
let xmargin = (upwidth as f64 - tmp_width).round();
|
|
|
|
|
|
let width = if int_width % 10 > 6 {
|
|
|
upwidth + 10
|
|
|
} else {
|
|
|
upwidth
|
|
|
};
|
|
|
|
|
|
(
|
|
|
width,
|
|
|
-((description.left_bound() - (xmargin / 2.0)).round() as i64),
|
|
|
)
|
|
|
};
|
|
|
|
|
|
// 计算图纸的高度,y热点坐标
|
|
|
//The below calculation for height and hotspot_y are taken from the qet source code
|
|
|
let (height, hotspot_y) = {
|
|
|
let tmp_height = description.bot_bound() - description.top_bound();
|
|
|
let int_height = tmp_height.round() as i64;
|
|
|
let upheight = ((int_height / 10) * 10) + 10;
|
|
|
let ymargin = (upheight as f64 - tmp_height).round();
|
|
|
|
|
|
let height = if int_height % 10 > 6 {
|
|
|
upheight + 10
|
|
|
} else {
|
|
|
upheight
|
|
|
};
|
|
|
|
|
|
(
|
|
|
height,
|
|
|
-((description.top_bound() - (ymargin / 2.0)).round() as i64),
|
|
|
)
|
|
|
};
|
|
|
|
|
|
// Definition结构体初始化 :设置所有必要字段,包括类型、版本、UUID等
|
|
|
Definition {
|
|
|
r#type: ItemType::Element,
|
|
|
width,
|
|
|
height,
|
|
|
hotspot_x,
|
|
|
hotspot_y,
|
|
|
version: "0.8.0".into(),
|
|
|
link_type: LinkType::Simple,
|
|
|
uuid: Uuid::new_v4().into(),
|
|
|
names: Names {
|
|
|
names: vec![Name {
|
|
|
lang: "en".into(),
|
|
|
value: name.into(), //need to truncate the extension
|
|
|
}],
|
|
|
},
|
|
|
element_infos: None,
|
|
|
informations: "Created using dxf2elmt!",
|
|
|
description,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 根据不同的测量单位返回相应的缩放因子
|
|
|
fn scale_factor(unit: Units) -> f64 {
|
|
|
//so per discussion at https://qelectrotech.org/forum/viewtopic.php?pid=20685#p20685
|
|
|
//we are in agreement to scale things to 1mm = 2px;
|
|
|
//all the below values are the converted equivalent of 2px per 1mm in the designated unit
|
|
|
//unit conversions taken from: https://www.unitconverters.net/length-converter.html
|
|
|
match unit {
|
|
|
Units::Unitless => 1.0, //for now if the drawing is untiless don't scale it
|
|
|
Units::Inches => 50.8,
|
|
|
Units::Feet => 609.6,
|
|
|
Units::Miles | Units::USSurveyMile => 3_218_694.437_4,
|
|
|
Units::Millimeters => 2.0,
|
|
|
Units::Centimeters => 20.0,
|
|
|
Units::Meters => 2_000.0,
|
|
|
Units::Kilometers => 2_000_000.0,
|
|
|
Units::Microinches => 50.8E-6,
|
|
|
Units::Mils => 0.0508,
|
|
|
Units::Yards => 1_828.8,
|
|
|
Units::Angstroms => 2.0E-7,
|
|
|
Units::Nanometers => 2.0e-6,
|
|
|
Units::Microns => 0.002,
|
|
|
Units::Decimeters => 200.0,
|
|
|
Units::Decameters => 20_000.0,
|
|
|
Units::Hectometers => 200_000.0,
|
|
|
Units::Gigameters => 2.0e12,
|
|
|
Units::AstronomicalUnits => 299_195_741_382_000.0,
|
|
|
Units::LightYears => 18_921_460_945_160_086_000.0,
|
|
|
Units::Parsecs => 61_713_551_625_599_170_000.0,
|
|
|
Units::USSurveyFeet => 609.601_219_2,
|
|
|
Units::USSurveyInch => 50.800_101_6,
|
|
|
|
|
|
//I'm finding very little references to US Survey yard at all. The only real
|
|
|
//link I could find was on the Wikipedia page for the Yard, which stated:
|
|
|
//"The US survey yard is very slightly longer." and linked to the US Survey Foot page
|
|
|
//I'll assume for now that 1 US Survey Yard is equal to 3 US Survey Feet. Which seems
|
|
|
//like a reasonable assumption, and would result in something slightly larger than a yard
|
|
|
Units::USSurveyYard => 1_828.803_657_6,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 将 Definition 结构体转换为 XML 元素
|
|
|
impl From<&Definition> for XMLElement {
|
|
|
// 创建 <definition> 根元素
|
|
|
fn from(def: &Definition) -> Self {
|
|
|
let mut def_xml = XMLElement::new("definition");
|
|
|
def_xml.add_attribute("height", def.height);
|
|
|
def_xml.add_attribute("width", def.width);
|
|
|
def_xml.add_attribute("hotspot_x", def.hotspot_x);
|
|
|
def_xml.add_attribute("hotspot_y", def.hotspot_y);
|
|
|
def_xml.add_attribute("version", &def.version);
|
|
|
def_xml.add_attribute("link_type", &def.link_type);
|
|
|
def_xml.add_attribute("type", &def.r#type);
|
|
|
|
|
|
def_xml.add_child((&def.uuid).into());
|
|
|
def_xml.add_child((&def.names).into());
|
|
|
if let Some(einfos) = &def.element_infos {
|
|
|
def_xml.add_child(einfos.into());
|
|
|
}
|
|
|
|
|
|
let mut info_elmt = XMLElement::new("informations");
|
|
|
info_elmt.add_text(def.informations);
|
|
|
def_xml.add_child(info_elmt);
|
|
|
|
|
|
def_xml.add_child((&def.description).into());
|
|
|
|
|
|
def_xml
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 枚举类型,表示各种图形对象(弧线、椭圆、多边形、文本、线条等)
|
|
|
#[derive(Debug)]
|
|
|
pub(crate) enum Objects {
|
|
|
Arc(Arc),
|
|
|
Ellipse(Ellipse),
|
|
|
Polygon(Polygon),
|
|
|
DynamicText(DynamicText),
|
|
|
Text(Text),
|
|
|
Line(Line),
|
|
|
//Terminal(Terminal),
|
|
|
Group(Vec<Objects>),
|
|
|
}
|
|
|
|
|
|
impl Objects {
|
|
|
// 返回一个 Descendants 迭代器,用于遍历对象的所有后代节
|
|
|
pub fn descendants(&self) -> Descendants<'_> {
|
|
|
Descendants {
|
|
|
stack: vec![self.children()],
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 返回一个 Children 迭代器,用于遍历对象的直接子节点
|
|
|
pub fn children(&self) -> Children<'_> {
|
|
|
match self {
|
|
|
Objects::Group(l) => Children { slice: l.iter() },
|
|
|
_ => Children { slice: [].iter() },
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 结构体,包含一个 stack 字段,存储 Children 迭代器的栈,使用生命周期参数 'a 确保引用的有效性
|
|
|
pub(crate) struct Descendants<'a> {
|
|
|
stack: Vec<Children<'a>>,
|
|
|
}
|
|
|
|
|
|
// Descendants迭代器实现,深度优先遍历 :使用栈结构实现树形结构的深度优先搜索
|
|
|
impl<'a> Iterator for Descendants<'a> {
|
|
|
type Item = &'a Objects;
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
//let iter_span = span!(Level::TRACE, "Iterating Object Descendants");
|
|
|
//let _span_guard = iter_span.enter();
|
|
|
while let Some(last) = self.stack.last_mut() {
|
|
|
if let Some(obj) = last.next() {
|
|
|
//trace!("Found more children");
|
|
|
self.stack.push(obj.children());
|
|
|
return Some(obj);
|
|
|
}
|
|
|
|
|
|
self.stack.pop();
|
|
|
}
|
|
|
None
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Children结构体,提供对直接子节点的简单迭代访
|
|
|
pub(crate) struct Children<'a> {
|
|
|
slice: std::slice::Iter<'a, Objects>,
|
|
|
}
|
|
|
|
|
|
// Children迭代器实现
|
|
|
impl<'a> Iterator for Children<'a> {
|
|
|
type Item = &'a Objects;
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
self.slice.next()
|
|
|
}
|
|
|
|
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
|
(self.slice.len(), None)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 实现Objects的ScaleEntity特征
|
|
|
impl ScaleEntity for Objects {
|
|
|
fn scale(&mut self, fact_x: f64, fact_y: f64) {
|
|
|
// match 是 Rust 的模式匹配,类似于 switch 语句但更强大
|
|
|
match self {
|
|
|
Objects::Arc(arc) => arc.scale(fact_x, fact_y),
|
|
|
Objects::Ellipse(ellipse) => ellipse.scale(fact_x, fact_y),
|
|
|
Objects::Polygon(polygon) => polygon.scale(fact_x, fact_y),
|
|
|
Objects::DynamicText(dynamic_text) => dynamic_text.scale(fact_x, fact_y),
|
|
|
Objects::Text(text) => text.scale(fact_x, fact_y),
|
|
|
Objects::Line(line) => line.scale(fact_x, fact_y),
|
|
|
Objects::Group(vec) => vec.iter_mut().for_each(|ob| ob.scale(fact_x, fact_y)),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fn left_bound(&self) -> f64 {
|
|
|
match self {
|
|
|
// left_bound就是x值
|
|
|
Objects::Arc(arc) => arc.left_bound(),
|
|
|
Objects::Ellipse(ellipse) => ellipse.left_bound(),
|
|
|
Objects::Polygon(polygon) => polygon.left_bound(),
|
|
|
Objects::DynamicText(dynamic_text) => dynamic_text.left_bound(),
|
|
|
Objects::Text(text) => text.left_bound(),
|
|
|
Objects::Line(line) => line.left_bound(),
|
|
|
Objects::Group(vec) => {
|
|
|
let lb = vec.iter().min_by(|ob1, ob2| {
|
|
|
ob1.left_bound()
|
|
|
.partial_cmp(&ob2.left_bound())
|
|
|
.unwrap_or(std::cmp::Ordering::Greater)
|
|
|
});
|
|
|
|
|
|
if let Some(lb) = lb {
|
|
|
lb.left_bound()
|
|
|
} else {
|
|
|
0.0
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fn right_bound(&self) -> f64 {
|
|
|
match self {
|
|
|
// right_bound就是x + width值
|
|
|
Objects::Arc(arc) => arc.right_bound(),
|
|
|
Objects::Ellipse(ellipse) => ellipse.right_bound(),
|
|
|
Objects::Polygon(polygon) => polygon.right_bound(),
|
|
|
Objects::DynamicText(dynamic_text) => dynamic_text.right_bound(),
|
|
|
Objects::Text(text) => text.right_bound(),
|
|
|
Objects::Line(line) => line.right_bound(),
|
|
|
Objects::Group(vec) => {
|
|
|
let rb = vec.iter().max_by(|ob1, ob2| {
|
|
|
ob1.right_bound()
|
|
|
.partial_cmp(&ob2.right_bound())
|
|
|
.unwrap_or(std::cmp::Ordering::Less)
|
|
|
});
|
|
|
|
|
|
if let Some(rb) = rb {
|
|
|
rb.right_bound()
|
|
|
} else {
|
|
|
0.0
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fn top_bound(&self) -> f64 {
|
|
|
match self {
|
|
|
Objects::Arc(arc) => arc.top_bound(),
|
|
|
Objects::Ellipse(ellipse) => ellipse.top_bound(),
|
|
|
Objects::Polygon(polygon) => polygon.top_bound(),
|
|
|
Objects::DynamicText(dynamic_text) => dynamic_text.top_bound(),
|
|
|
Objects::Text(text) => text.top_bound(),
|
|
|
Objects::Line(line) => line.top_bound(),
|
|
|
Objects::Group(vec) => {
|
|
|
let tb = vec.iter().min_by(|ob1, ob2| {
|
|
|
ob1.top_bound()
|
|
|
.partial_cmp(&ob2.top_bound())
|
|
|
.unwrap_or(std::cmp::Ordering::Greater)
|
|
|
});
|
|
|
|
|
|
if let Some(tb) = tb {
|
|
|
tb.top_bound()
|
|
|
} else {
|
|
|
0.0
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fn bot_bound(&self) -> f64 {
|
|
|
match self {
|
|
|
Objects::Arc(arc) => arc.bot_bound(),
|
|
|
Objects::Ellipse(ellipse) => ellipse.bot_bound(),
|
|
|
Objects::Polygon(polygon) => polygon.bot_bound(),
|
|
|
Objects::DynamicText(dynamic_text) => dynamic_text.bot_bound(),
|
|
|
Objects::Text(text) => text.bot_bound(),
|
|
|
Objects::Line(line) => line.bot_bound(),
|
|
|
Objects::Group(vec) => {
|
|
|
let bb = vec.iter().max_by(|ob1, ob2| {
|
|
|
ob1.bot_bound()
|
|
|
.partial_cmp(&ob2.bot_bound())
|
|
|
.unwrap_or(std::cmp::Ordering::Less)
|
|
|
});
|
|
|
|
|
|
if let Some(bb) = bb {
|
|
|
bb.bot_bound()
|
|
|
} else {
|
|
|
0.0
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
struct ScaleFactor {
|
|
|
x: f64,
|
|
|
y: f64,
|
|
|
}
|
|
|
|
|
|
impl Default for ScaleFactor {
|
|
|
fn default() -> Self {
|
|
|
ScaleFactor { x: 1.0, y: 1.0 }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Default, Debug)]
|
|
|
pub(crate) struct Offset {
|
|
|
x: f64,
|
|
|
y: f64,
|
|
|
}
|
|
|
|
|
|
// 构建器结构体
|
|
|
#[derive(Debug)]
|
|
|
pub struct ObjectsBuilder<'a> {
|
|
|
// &'a Entity 是对 Entity 的引用
|
|
|
ent: &'a Entity,
|
|
|
spline_step: u32,
|
|
|
blocks: &'a [&'a Block],
|
|
|
offset: Offset,
|
|
|
scale_fact: ScaleFactor
|
|
|
}
|
|
|
|
|
|
impl<'a> ObjectsBuilder<'a> {
|
|
|
// 创建新的实例
|
|
|
pub fn new(ent: &'a Entity, spline_step: u32) -> Self {
|
|
|
Self {
|
|
|
ent,
|
|
|
spline_step,
|
|
|
blocks: &[],
|
|
|
offset: Offset::default(),
|
|
|
scale_fact: ScaleFactor::default(),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 设置可用的块引用列表
|
|
|
pub fn blocks(self, blocks: &'a [&'a Block]) -> Self {
|
|
|
Self { blocks, ..self }
|
|
|
}
|
|
|
|
|
|
// 设置 X、Y 坐标偏移
|
|
|
pub fn offsets(self, x: f64, y: f64) -> Self {
|
|
|
Self {
|
|
|
offset: Offset { x, y },
|
|
|
..self
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 设置 X、Y 缩放因子
|
|
|
pub fn scaling(self, fact_x: f64, fact_y: f64) -> Self {
|
|
|
Self {
|
|
|
scale_fact: ScaleFactor {
|
|
|
x: fact_x,
|
|
|
y: fact_y,
|
|
|
},
|
|
|
..self
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[allow(clippy::too_many_lines)]
|
|
|
pub fn build(self) -> Result<Objects, &'static str /*add better error later*/> {
|
|
|
// 检查entity的is_visible属性,如果为false则不进行转换
|
|
|
if !self.ent.common.is_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_type_name == "INTERROMPUx2" {
|
|
|
// 使用正则表达式替换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 {
|
|
|
// Circle → Ellipse :圆转换为椭圆
|
|
|
EntityType::Circle(circle) => {
|
|
|
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.x += self.offset.x;
|
|
|
ellipse.y -= self.offset.y;
|
|
|
Ok(Objects::Ellipse(ellipse))
|
|
|
}
|
|
|
EntityType::Line(line) => {
|
|
|
// 获取颜色
|
|
|
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.x1 += self.offset.x;
|
|
|
line.y1 -= self.offset.y;
|
|
|
|
|
|
line.x2 += self.offset.x;
|
|
|
line.y2 -= self.offset.y;
|
|
|
|
|
|
|
|
|
Ok(Objects::Line(line))
|
|
|
}
|
|
|
EntityType::Arc(arc) => {
|
|
|
// 获取颜色
|
|
|
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.x += self.offset.x;
|
|
|
arc.y -= self.offset.y;
|
|
|
|
|
|
Ok(Objects::Arc(arc))
|
|
|
}
|
|
|
EntityType::Spline(spline) => {
|
|
|
let mut poly: Polygon = (spline, self.spline_step).into();
|
|
|
|
|
|
// 根据line_type_name更新线型样式
|
|
|
poly.update_line_style(&update_line_style);
|
|
|
|
|
|
match poly.coordinates.len() {
|
|
|
0 | 1 => Err("Error removing empty Spline"),
|
|
|
//I'll need to improve my understanding of splines and the math here
|
|
|
//to make sure I do this correctly.
|
|
|
//2 => //convert to line
|
|
|
_ => {
|
|
|
poly.scale(self.scale_fact.x, self.scale_fact.y);
|
|
|
for cord in &mut poly.coordinates {
|
|
|
cord.x += self.offset.x;
|
|
|
cord.y -= self.offset.y;
|
|
|
}
|
|
|
|
|
|
Ok(Objects::Polygon(poly))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
EntityType::Text(text) => {
|
|
|
Ok(
|
|
|
//right now the dxf2elmt defaults to making all text Static Text...
|
|
|
//it was requested by the QET devs to add in support for Dynamic text
|
|
|
//which was added, but it defaults to OFF, and QET doesn't pass the parameter
|
|
|
//to enable it...I'm wondering if it makes more sense to default to use dynamic text
|
|
|
//for now I'll set it to use dynamic text, and once I get the CLI flag passing through
|
|
|
//I might change the default parameter to use Dynamic Text
|
|
|
if false {
|
|
|
//how best to pass in the flag for dynamic text or not....should the flag also default to true?
|
|
|
let mut text: Text = (
|
|
|
text,
|
|
|
{
|
|
|
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();
|
|
|
|
|
|
text.scale(self.scale_fact.x, self.scale_fact.y);
|
|
|
|
|
|
text.x += self.offset.x;
|
|
|
text.y -= self.offset.y;
|
|
|
|
|
|
Objects::Text(text)
|
|
|
} else {
|
|
|
let mut dtext = DTextBuilder::from_text(text)
|
|
|
.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();
|
|
|
|
|
|
dtext.scale(self.scale_fact.x, self.scale_fact.y);
|
|
|
|
|
|
dtext.x += self.offset.x;
|
|
|
dtext.y -= self.offset.y;
|
|
|
|
|
|
Objects::DynamicText(dtext)
|
|
|
},
|
|
|
)
|
|
|
}
|
|
|
EntityType::Ellipse(ellipse) => {
|
|
|
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.x += self.offset.x;
|
|
|
ellipse.y -= self.offset.y;
|
|
|
|
|
|
Ok(Objects::Ellipse(ellipse))
|
|
|
}
|
|
|
EntityType::MText(mtext) => {
|
|
|
Ok(
|
|
|
//right now the dxf2elmt defaults to making all text Static Text...
|
|
|
//it was requested by the QET devs to add in support for Dynamic text
|
|
|
//which was added, but it defaults to OFF, and QET doesn't pass the parameter
|
|
|
//to enable it...I'm wondering if it makes more sense to default to use dynamic text
|
|
|
//for now I'll set it to use dynamic text, and once I get the CLI flag passing through
|
|
|
//I might change the default parameter to use Dynamic Text
|
|
|
if false {
|
|
|
//how best to pass in the flag for dynamic text or not....should the flag also default to true?
|
|
|
/*let mut text: Text =
|
|
|
(mtext, HexColor::from_u32(ent.common.color_24_bit as u32)).into();
|
|
|
text.x += offset_x;
|
|
|
text.y -= offset_y;
|
|
|
Objects::Text(text)*/
|
|
|
todo!();
|
|
|
} else {
|
|
|
let mut dtext = DTextBuilder::from_mtext(mtext)
|
|
|
.color(HexColor::from_u32(self.ent.common.color_24_bit as u32))
|
|
|
.build();
|
|
|
|
|
|
dtext.scale(self.scale_fact.x, self.scale_fact.y);
|
|
|
|
|
|
dtext.x += self.offset.x;
|
|
|
dtext.y -= self.offset.y;
|
|
|
|
|
|
Objects::DynamicText(dtext)
|
|
|
},
|
|
|
)
|
|
|
}
|
|
|
EntityType::Polyline(polyline) => match polyline.__vertices_and_handles.len() {
|
|
|
0 | 1 => Err("Error empty Polyline"),
|
|
|
2 => {
|
|
|
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.x1 += self.offset.x;
|
|
|
line.y1 -= self.offset.y;
|
|
|
|
|
|
line.x2 += self.offset.x;
|
|
|
line.y2 -= self.offset.y;
|
|
|
|
|
|
Ok(Objects::Line(line))
|
|
|
}
|
|
|
_ => {
|
|
|
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.x += self.offset.x;
|
|
|
ellipse.y -= self.offset.y;
|
|
|
|
|
|
Ok(Objects::Ellipse(ellipse))
|
|
|
} else {
|
|
|
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);
|
|
|
|
|
|
for cord in &mut poly.coordinates {
|
|
|
cord.x += self.offset.x;
|
|
|
cord.y -= self.offset.y;
|
|
|
}
|
|
|
|
|
|
Ok(Objects::Polygon(poly))
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
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"),
|
|
|
2 => {
|
|
|
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.x1 += self.offset.x;
|
|
|
line.y1 -= self.offset.y;
|
|
|
|
|
|
line.x2 += self.offset.x;
|
|
|
line.y2 -= self.offset.y;
|
|
|
|
|
|
Ok(Objects::Line(line))
|
|
|
}
|
|
|
_ => {
|
|
|
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.x += self.offset.x;
|
|
|
ellipse.y -= self.offset.y;
|
|
|
|
|
|
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 {
|
|
|
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);
|
|
|
|
|
|
for cord in &mut poly.coordinates {
|
|
|
cord.x += self.offset.x;
|
|
|
cord.y -= self.offset.y;
|
|
|
}
|
|
|
|
|
|
Ok(Objects::Polygon(poly))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
EntityType::Solid(solid) => {
|
|
|
let mut poly: Polygon = solid.into();
|
|
|
|
|
|
poly.scale(self.scale_fact.x, self.scale_fact.y);
|
|
|
|
|
|
for cord in &mut poly.coordinates {
|
|
|
cord.x += self.offset.x;
|
|
|
cord.y -= self.offset.y;
|
|
|
}
|
|
|
|
|
|
Ok(Objects::Polygon(poly))
|
|
|
}
|
|
|
EntityType::Insert(ins) => {
|
|
|
//info!("Found an Insert Block: {ins:?}");
|
|
|
info!("Found an Insert Block: {}", &ins.name);
|
|
|
let Some(block) = self.blocks.iter().find(|bl| bl.name == ins.name) else {
|
|
|
error!("Block {} not found", ins.name);
|
|
|
return Err("Block Not Found");
|
|
|
};
|
|
|
trace!(
|
|
|
"Base Point: x: {} / y: {}",
|
|
|
block.base_point.x,
|
|
|
block.base_point.y
|
|
|
);
|
|
|
|
|
|
trace!("Creating Group from block {}. Pos(x:{}, y:{}). Offset(x:{}, y:{}). Scale(x:{}, y:{})",
|
|
|
ins.name, ins.location.x, ins.location.y, self.offset.x, self.offset.y, self.scale_fact.x * ins.x_scale_factor,
|
|
|
self.scale_fact.y * ins.y_scale_factor);
|
|
|
Ok(Objects::Group(
|
|
|
block
|
|
|
.entities
|
|
|
.iter()
|
|
|
.filter_map(|ent| {
|
|
|
ObjectsBuilder::new(ent, self.spline_step)
|
|
|
.offsets(
|
|
|
ins.location.x - block.base_point.x,
|
|
|
ins.location.y - block.base_point.y,
|
|
|
)
|
|
|
.scaling(
|
|
|
self.scale_fact.x * ins.x_scale_factor,
|
|
|
self.scale_fact.y * ins.y_scale_factor,
|
|
|
)
|
|
|
.blocks(self.blocks)
|
|
|
.build()
|
|
|
.ok()
|
|
|
})
|
|
|
.collect(),
|
|
|
))
|
|
|
}
|
|
|
EntityType::Leader(leader) => {
|
|
|
let ld: Leader = leader.into();
|
|
|
|
|
|
Ok(Objects::Group(
|
|
|
ld.0.into_iter()
|
|
|
.map(|mut ln| {
|
|
|
ln.scale(self.scale_fact.x, self.scale_fact.y);
|
|
|
|
|
|
ln.x1 += self.offset.x;
|
|
|
ln.y1 -= self.offset.y;
|
|
|
|
|
|
ln.x2 += self.offset.x;
|
|
|
ln.y2 -= self.offset.y;
|
|
|
|
|
|
Objects::Line(ln)
|
|
|
})
|
|
|
.collect(),
|
|
|
))
|
|
|
}
|
|
|
EntityType::AttributeDefinition(attrib) => Ok({
|
|
|
//need to look up the proper way to get the color for the Attrib
|
|
|
let mut dtext = DTextBuilder::from_attrib(attrib)
|
|
|
.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();
|
|
|
|
|
|
dtext.scale(self.scale_fact.x, self.scale_fact.y);
|
|
|
|
|
|
dtext.x += self.offset.x;
|
|
|
dtext.y -= self.offset.y;
|
|
|
|
|
|
Objects::DynamicText(dtext)
|
|
|
}),
|
|
|
_ => {
|
|
|
//dbg!(&self.ent.specific);
|
|
|
Err("Need to implement the rest of the entity types")
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 将 Objects 枚举转换为 Either<XMLElement, Vec<XMLElement>> 类型
|
|
|
// Either::Left(XMLElement) :单个 XML 元素
|
|
|
// Either::Right(Vec<XMLElement>) :XML 元素向量
|
|
|
impl From<&Objects> for Either<XMLElement, Vec<XMLElement>> {
|
|
|
fn from(obj: &Objects) -> Self {
|
|
|
match obj {
|
|
|
Objects::Arc(arc) => Either::Left(arc.into()),
|
|
|
Objects::Ellipse(ell) => Either::Left(ell.into()),
|
|
|
Objects::Polygon(poly) => Either::Left(poly.into()),
|
|
|
Objects::DynamicText(dtext) => Either::Left(dtext.into()),
|
|
|
Objects::Text(txt) => Either::Left(txt.into()),
|
|
|
Objects::Line(line) => Either::Left(line.into()),
|
|
|
Objects::Group(block) => Either::Right(
|
|
|
// 迭代组中的每个对象,try_from转换,filter_map和ok忽略转换失败对象,collect()收集转换成功元素
|
|
|
block
|
|
|
.iter()
|
|
|
//.flatten()
|
|
|
.filter_map(|obj| XMLElement::try_from(obj).ok())
|
|
|
.collect(),
|
|
|
),
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 实现try_from尝试转换
|
|
|
impl TryFrom<&Objects> for XMLElement {
|
|
|
type Error = &'static str; // add better error later
|
|
|
|
|
|
fn try_from(obj: &Objects) -> Result<Self, Self::Error> {
|
|
|
match obj {
|
|
|
Objects::Arc(arc) => Ok(arc.into()),
|
|
|
Objects::Ellipse(ell) => Ok(ell.into()),
|
|
|
Objects::Polygon(poly) => Ok(poly.into()),
|
|
|
Objects::DynamicText(dtext) => Ok(dtext.into()),
|
|
|
Objects::Text(txt) => Ok(txt.into()),
|
|
|
Objects::Line(line) => Ok(line.into()),
|
|
|
Objects::Group(_) => Err("Unsupported"),
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 包含所有图形对象的描述
|
|
|
#[derive(Debug)]
|
|
|
pub struct Description {
|
|
|
objects: Vec<Objects>,
|
|
|
}
|
|
|
|
|
|
// 实现Description的ScaleEntity特征
|
|
|
impl ScaleEntity for Description {
|
|
|
fn scale(&mut self, fact_x: f64, fact_y: f64) {
|
|
|
self.objects
|
|
|
.iter_mut()
|
|
|
.for_each(|ob| ob.scale(fact_x, fact_y));
|
|
|
}
|
|
|
|
|
|
fn left_bound(&self) -> f64 {
|
|
|
// 找到所有对象中left_bound值最小的那个对象的
|
|
|
let lb = self.objects.iter().min_by(|ob1, ob2| {
|
|
|
ob1.left_bound()
|
|
|
.partial_cmp(&ob2.left_bound())
|
|
|
.unwrap_or(std::cmp::Ordering::Greater)
|
|
|
});
|
|
|
|
|
|
// 检查是否找到了对象,如果找到了对象,返回该对象的左边界值
|
|
|
if let Some(lb) = lb {
|
|
|
lb.left_bound()
|
|
|
} else { // 如果没有找到任何对象(objects为空),返回默认值0.0
|
|
|
0.0
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fn right_bound(&self) -> f64 {
|
|
|
let rb = self.objects.iter().max_by(|ob1, ob2| {
|
|
|
ob1.right_bound()
|
|
|
.partial_cmp(&ob2.right_bound())
|
|
|
.unwrap_or(std::cmp::Ordering::Less)
|
|
|
});
|
|
|
|
|
|
if let Some(rb) = rb {
|
|
|
rb.left_bound()
|
|
|
} else {
|
|
|
0.0
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fn top_bound(&self) -> f64 {
|
|
|
let tb = self.objects.iter().min_by(|ob1, ob2| {
|
|
|
ob1.top_bound()
|
|
|
.partial_cmp(&ob2.top_bound())
|
|
|
.unwrap_or(std::cmp::Ordering::Greater)
|
|
|
});
|
|
|
|
|
|
if let Some(tb) = tb {
|
|
|
tb.top_bound()
|
|
|
} else {
|
|
|
0.0
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fn bot_bound(&self) -> f64 {
|
|
|
let bb = self.objects.iter().max_by(|ob1, ob2| {
|
|
|
ob1.bot_bound()
|
|
|
.partial_cmp(&ob2.bot_bound())
|
|
|
.unwrap_or(std::cmp::Ordering::Less)
|
|
|
});
|
|
|
|
|
|
if let Some(bb) = bb {
|
|
|
bb.top_bound()
|
|
|
} else {
|
|
|
0.0
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 实现description转xml
|
|
|
impl From<&Description> for XMLElement {
|
|
|
fn from(desc: &Description) -> Self {
|
|
|
let mut desc_xml = XMLElement::new("description");
|
|
|
// 遍历description中的obj,即arc,line,text那些
|
|
|
for obj in &desc.objects {
|
|
|
if let Ok(elem) = XMLElement::try_from(obj) {
|
|
|
desc_xml.add_child(elem);
|
|
|
}
|
|
|
for obj in obj.descendants() {
|
|
|
if let Ok(elem) = XMLElement::try_from(obj) {
|
|
|
desc_xml.add_child(elem);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
desc_xml
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/*impl TryFrom<Drawing> for Description {
|
|
|
type Error = &'static str; //add better error later
|
|
|
|
|
|
fn try_from(drw: Drawing) -> Result<Self, Self::Error> {
|
|
|
drw.entities().filter_map(|ent| Objects::try_from(ent).ok()).collect();
|
|
|
}
|
|
|
}*/
|
|
|
// 将 DXF 绘图对象 ( Drawing ) 和样条步长参数 ( u32 ) 转换为 Description 结构体
|
|
|
// drw: &Drawing :DXF 绘图对象的引用
|
|
|
// spline_step: u32 :样条曲线的细分步长参数
|
|
|
impl From<(&Drawing, u32)> for Description {
|
|
|
fn from((drw, spline_step): (&Drawing, u32)) -> Self {
|
|
|
// 创建跟踪范围,用于调试和性能分析
|
|
|
let _from_drw_span = span!(Level::TRACE, "Converting Drawing to Description");
|
|
|
|
|
|
|
|
|
Self {
|
|
|
// 遍历绘图中的所有实体,使用 filter_map 进行转换和过滤
|
|
|
objects: drw
|
|
|
.entities()
|
|
|
.filter_map(|ent| match &ent.specific {
|
|
|
EntityType::Insert(ins) => {
|
|
|
// 检查Insert类型entity的is_visible属性
|
|
|
if !ent.common.is_visible {
|
|
|
return None;
|
|
|
}
|
|
|
|
|
|
// 使用 find_block(drw, &ins.name) 查找对应的块定义
|
|
|
let block = find_block(drw, &ins.name)?;
|
|
|
// 收集所有可用的块引用
|
|
|
let blocks: Vec<&Block> = drw.blocks().collect();
|
|
|
trace!(
|
|
|
"Creating Group from block {}. Pos(x:{}, y:{}). Scale(x:{}, y:{})",
|
|
|
ins.name,
|
|
|
ins.location.x,
|
|
|
ins.location.y,
|
|
|
ins.x_scale_factor,
|
|
|
ins.y_scale_factor
|
|
|
);
|
|
|
// 递归遍历block中的entities,使用 ObjectsBuilder 构建 Objects 结构体
|
|
|
Some(Objects::Group(
|
|
|
block
|
|
|
.entities
|
|
|
.iter()
|
|
|
.filter_map(|ent| {
|
|
|
ObjectsBuilder::new(ent, spline_step)
|
|
|
//very confused here, in one test file if I leave out the ins locations here it puts things in the
|
|
|
//wrong location, and puts them in the correct location when I add the ins location in.
|
|
|
//but in another file it's the opposite, not sure why the difference...
|
|
|
.offsets(ins.location.x, ins.location.y)
|
|
|
.scaling(ins.x_scale_factor, ins.y_scale_factor)
|
|
|
.blocks(&blocks)
|
|
|
.build()
|
|
|
.ok()
|
|
|
})
|
|
|
.collect(),
|
|
|
))
|
|
|
}
|
|
|
// 处理其他非Block类型的实体
|
|
|
_ => ObjectsBuilder::new(ent, spline_step).build().ok(),
|
|
|
})
|
|
|
.collect(),
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//probably don't need to worry about this as they won't exist in the dxf...
|
|
|
/*pub struct Terminal {
|
|
|
x: f64,
|
|
|
y: f64,
|
|
|
uuid: Uuid,
|
|
|
name: String,
|
|
|
orientation: TermOrient,
|
|
|
//type?
|
|
|
// Generic
|
|
|
// Indoor Terminal Block
|
|
|
// External Terminal Block
|
|
|
}*/
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
pub struct Names {
|
|
|
names: Vec<Name>,
|
|
|
}
|
|
|
|
|
|
impl From<&Names> for XMLElement {
|
|
|
fn from(nme: &Names) -> Self {
|
|
|
let mut names_elmt = XMLElement::new("names");
|
|
|
for name in &nme.names {
|
|
|
let mut nm_elmt = XMLElement::new("name");
|
|
|
nm_elmt.add_attribute("lang", &name.lang);
|
|
|
nm_elmt.add_text(&name.value);
|
|
|
names_elmt.add_child(nm_elmt);
|
|
|
}
|
|
|
names_elmt
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
pub struct Name {
|
|
|
lang: String, //should this be an enum of language shorts at some point, maybe not worth it
|
|
|
value: String,
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
pub struct ElmtUuid {
|
|
|
uuid: Uuid,
|
|
|
}
|
|
|
|
|
|
impl From<Uuid> for ElmtUuid {
|
|
|
fn from(uuid: Uuid) -> Self {
|
|
|
ElmtUuid { uuid }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl From<&ElmtUuid> for XMLElement {
|
|
|
fn from(uuid: &ElmtUuid) -> Self {
|
|
|
let mut uuid_xml = XMLElement::new("uuid");
|
|
|
uuid_xml.add_attribute("uuid", format!("{{{}}}", uuid.uuid));
|
|
|
uuid_xml
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//I need to check what these other item types are used for. I think it's unlikely
|
|
|
//I'll ever need for this tool, so it might be worth just hard coding the "element"
|
|
|
//string when writing out the XML, but for now I'll just comment out the other enum
|
|
|
//variants to suppress the clippy warnings.
|
|
|
#[derive(Debug)]
|
|
|
enum ItemType {
|
|
|
Element = 1,
|
|
|
/*ElementsCategory = 2,
|
|
|
ElementsCollection = 4,
|
|
|
ElementsContainer = 6,
|
|
|
ElementsCollectionItem = 7,
|
|
|
TitleBlockTemplate = 8,
|
|
|
TitleBlockTemplatesCollection = 16,
|
|
|
TitleBlockTemplatesCollectionItem = 24,
|
|
|
Diagram = 32,
|
|
|
Project = 64,
|
|
|
All = 127,*/
|
|
|
}
|
|
|
|
|
|
impl Display for ItemType {
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
write!(
|
|
|
f,
|
|
|
"{}",
|
|
|
match self {
|
|
|
Self::Element => "element",
|
|
|
/*Self::ElementsCategory | Self::ElementsContainer | Self::ElementsCollectionItem =>
|
|
|
"elements category",
|
|
|
Self::ElementsCollection => "element",
|
|
|
Self::TitleBlockTemplate | Self::TitleBlockTemplatesCollectionItem =>
|
|
|
"title block template",
|
|
|
Self::TitleBlockTemplatesCollection => "title block templates collection",
|
|
|
Self::Diagram => "diagram",
|
|
|
Self::Project => "project",
|
|
|
Self::All => "All",*/
|
|
|
}
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
enum HAlignment {
|
|
|
Left,
|
|
|
Center,
|
|
|
Right,
|
|
|
}
|
|
|
|
|
|
impl Display for HAlignment {
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
write!(
|
|
|
f,
|
|
|
"{}",
|
|
|
match self {
|
|
|
Self::Left => "AlignLeft",
|
|
|
Self::Center => "AlignHCenter",
|
|
|
Self::Right => "AlignRight",
|
|
|
}
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl From<AttachmentPoint> for HAlignment {
|
|
|
fn from(value: AttachmentPoint) -> Self {
|
|
|
match value {
|
|
|
AttachmentPoint::TopLeft
|
|
|
| AttachmentPoint::MiddleLeft
|
|
|
| AttachmentPoint::BottomLeft => HAlignment::Left,
|
|
|
AttachmentPoint::TopCenter
|
|
|
| AttachmentPoint::MiddleCenter
|
|
|
| AttachmentPoint::BottomCenter => HAlignment::Center,
|
|
|
AttachmentPoint::TopRight
|
|
|
| AttachmentPoint::MiddleRight
|
|
|
| AttachmentPoint::BottomRight => HAlignment::Right,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl From<HorizontalTextJustification> for HAlignment {
|
|
|
fn from(value: HorizontalTextJustification) -> Self {
|
|
|
//https://ezdxf.readthedocs.io/en/stable/tutorials/text.html#tut-text
|
|
|
match value {
|
|
|
HorizontalTextJustification::Left => HAlignment::Left,
|
|
|
HorizontalTextJustification::Center => HAlignment::Center,
|
|
|
HorizontalTextJustification::Middle => HAlignment::Center,
|
|
|
HorizontalTextJustification::Right => HAlignment::Right,
|
|
|
|
|
|
//TODO: Handling the Aligned Middle and Fit alignments are a bit more complicated
|
|
|
//for now I'll just default if it gets one of those we Alighn Left
|
|
|
_ => HAlignment::Left,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
enum VAlignment {
|
|
|
Top,
|
|
|
Center,
|
|
|
Bottom,
|
|
|
}
|
|
|
|
|
|
impl Display for VAlignment {
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
write!(
|
|
|
f,
|
|
|
"{}",
|
|
|
match self {
|
|
|
Self::Top => "AlignTop",
|
|
|
Self::Center => "AlignVCenter",
|
|
|
Self::Bottom => "AlignBottom",
|
|
|
}
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl From<AttachmentPoint> for VAlignment {
|
|
|
fn from(value: AttachmentPoint) -> Self {
|
|
|
match value {
|
|
|
AttachmentPoint::TopLeft | AttachmentPoint::TopCenter | AttachmentPoint::TopRight => {
|
|
|
VAlignment::Top
|
|
|
}
|
|
|
AttachmentPoint::MiddleLeft
|
|
|
| AttachmentPoint::MiddleCenter
|
|
|
| AttachmentPoint::MiddleRight => VAlignment::Center,
|
|
|
AttachmentPoint::BottomLeft
|
|
|
| AttachmentPoint::BottomCenter
|
|
|
| AttachmentPoint::BottomRight => VAlignment::Bottom,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl From<VerticalTextJustification> for VAlignment {
|
|
|
fn from(value: VerticalTextJustification) -> Self {
|
|
|
//https://ezdxf.readthedocs.io/en/stable/tutorials/text.html#tut-text
|
|
|
match value {
|
|
|
VerticalTextJustification::Top => VAlignment::Top,
|
|
|
VerticalTextJustification::Middle => VAlignment::Center,
|
|
|
VerticalTextJustification::Baseline | VerticalTextJustification::Bottom => {
|
|
|
VAlignment::Bottom
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
enum LineEnd {
|
|
|
None,
|
|
|
SimpleArrow,
|
|
|
/*TriangleArrow,
|
|
|
Circle,
|
|
|
Diamond,*/
|
|
|
}
|
|
|
|
|
|
impl Display for LineEnd {
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
write!(
|
|
|
f,
|
|
|
"{}",
|
|
|
match self {
|
|
|
Self::None => "none",
|
|
|
Self::SimpleArrow => "simple",
|
|
|
/*Self::TriangleArrow => "triangle",
|
|
|
Self::Circle => "circle",
|
|
|
Self::Diamond => "diamond",*/
|
|
|
}
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
enum LinkType {
|
|
|
Simple,
|
|
|
/*Master,
|
|
|
Slave,
|
|
|
NextReport,
|
|
|
PrevReport,
|
|
|
TermBlock,
|
|
|
Thumbnail,*/
|
|
|
}
|
|
|
|
|
|
impl Display for LinkType {
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
write!(
|
|
|
f,
|
|
|
"{}",
|
|
|
match self {
|
|
|
Self::Simple => "simple",
|
|
|
/*Self::Master => "master",
|
|
|
Self::Slave => "slave",
|
|
|
Self::NextReport => "next_report",
|
|
|
Self::PrevReport => "previous_report",
|
|
|
Self::TermBlock => "terminal",
|
|
|
Self::Thumbnail => "thumbnail",*/
|
|
|
}
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
pub struct ElemInfos {
|
|
|
elem_info: Vec<ElemInfo>,
|
|
|
}
|
|
|
|
|
|
impl From<&ElemInfos> for XMLElement {
|
|
|
fn from(elems: &ElemInfos) -> Self {
|
|
|
let mut elems_xml = XMLElement::new("elementInformations");
|
|
|
for elem in &elems.elem_info {
|
|
|
elems_xml.add_child(elem.into());
|
|
|
}
|
|
|
|
|
|
elems_xml
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
pub struct ElemInfo {
|
|
|
//there seems to be a list in the editor with the following values (per the XML)
|
|
|
// * supplier
|
|
|
// * description
|
|
|
// * machine_manufacturer_reference
|
|
|
// * manufacturer_reference
|
|
|
// * quantity
|
|
|
// * manufacturer
|
|
|
// * label
|
|
|
// * unity
|
|
|
// * plant
|
|
|
// * comment
|
|
|
// * designation
|
|
|
// But can it only ever be these values? Might need to dig into the code. For now I'll use a string
|
|
|
name: String,
|
|
|
|
|
|
//I would assume show would be a bool...but instead of a true value I'm getting a "1" in the XML
|
|
|
//generated by the element editor. Maybe this means something else? I'll use an i32 for now
|
|
|
show: i32,
|
|
|
|
|
|
value: String,
|
|
|
}
|
|
|
|
|
|
impl From<&ElemInfo> for XMLElement {
|
|
|
fn from(elem: &ElemInfo) -> Self {
|
|
|
let mut elem_xml = XMLElement::new("elementInformation");
|
|
|
elem_xml.add_attribute("show", elem.show);
|
|
|
elem_xml.add_attribute("name", &elem.name);
|
|
|
elem_xml.add_text(&elem.value);
|
|
|
|
|
|
elem_xml
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[inline]
|
|
|
pub fn two_dec(num: f64) -> f64 {
|
|
|
(num * 100.0).round() / 100.0
|
|
|
}
|
|
|
|
|
|
//Should be the relevant Qt5 Code for the font string in Qt5...
|
|
|
//Might need to look it up for Qt6, since it appears to have changed
|
|
|
//and add in support for either or?
|
|
|
|
|
|
/*https://codebrowser.dev/qt5/qtbase/src/gui/text/qfont.cpp.html
|
|
|
/*!
|
|
|
Returns a description of the font. The description is a
|
|
|
comma-separated list of the attributes, perfectly suited for use
|
|
|
in QSettings, and consists of the following:
|
|
|
\list
|
|
|
\li Font family
|
|
|
\li Point size
|
|
|
\li Pixel size
|
|
|
\li Style hint
|
|
|
\li Font weight
|
|
|
\li Font style
|
|
|
\li Underline
|
|
|
\li Strike out
|
|
|
\li Fixed pitch
|
|
|
\li Always \e{0}
|
|
|
\li Capitalization
|
|
|
\li Letter spacing
|
|
|
\li Word spacing
|
|
|
\li Stretch
|
|
|
\li Style strategy
|
|
|
\li Font style (omitted when unavailable)
|
|
|
\endlist
|
|
|
\sa fromString()
|
|
|
*/
|
|
|
QString QFont::toString() const
|
|
|
{
|
|
|
const QChar comma(QLatin1Char(','));
|
|
|
QString fontDescription = family() + comma +
|
|
|
QString::number( pointSizeF()) + comma +
|
|
|
QString::number( pixelSize()) + comma +
|
|
|
QString::number((int) styleHint()) + comma +
|
|
|
QString::number( weight()) + comma +
|
|
|
QString::number((int) style()) + comma +
|
|
|
QString::number((int) underline()) + comma +
|
|
|
QString::number((int) strikeOut()) + comma +
|
|
|
QString::number((int)fixedPitch()) + comma +
|
|
|
QString::number((int) false);
|
|
|
QString fontStyle = styleName();
|
|
|
if (!fontStyle.isEmpty())
|
|
|
fontDescription += comma + fontStyle;
|
|
|
return fontDescription;
|
|
|
}
|
|
|
*/
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
pub enum FontStyleHint {
|
|
|
Helvetica,
|
|
|
Times,
|
|
|
Courier,
|
|
|
OldEnglish,
|
|
|
System,
|
|
|
AnyStyle,
|
|
|
Cursive,
|
|
|
Monospace,
|
|
|
Fantasy,
|
|
|
}
|
|
|
|
|
|
/*impl FontStyleHint {
|
|
|
pub const SansSerif: FontStyleHint = FontStyleHint::Helvetica;
|
|
|
pub const Serif: FontStyleHint = FontStyleHint::Times;
|
|
|
pub const TypeWriter: FontStyleHint = FontStyleHint::Courier;
|
|
|
pub const Decorative: FontStyleHint = FontStyleHint::OldEnglish;
|
|
|
}
|
|
|
*/
|
|
|
|
|
|
impl From<&FontStyleHint> for i32 {
|
|
|
fn from(value: &FontStyleHint) -> Self {
|
|
|
match value {
|
|
|
FontStyleHint::Helvetica => 0,
|
|
|
FontStyleHint::Times => 1,
|
|
|
FontStyleHint::Courier => 2,
|
|
|
FontStyleHint::OldEnglish => 3,
|
|
|
FontStyleHint::System => 4,
|
|
|
FontStyleHint::AnyStyle => 5,
|
|
|
FontStyleHint::Cursive => 6,
|
|
|
FontStyleHint::Monospace => 7,
|
|
|
FontStyleHint::Fantasy => 8,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
pub enum FontStyle {
|
|
|
Normal,
|
|
|
Italic,
|
|
|
Oblique,
|
|
|
}
|
|
|
|
|
|
impl From<&FontStyle> for i32 {
|
|
|
fn from(value: &FontStyle) -> Self {
|
|
|
match value {
|
|
|
FontStyle::Normal => 0,
|
|
|
FontStyle::Italic => 1,
|
|
|
FontStyle::Oblique => 2,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
struct FontInfo {
|
|
|
family: String,
|
|
|
point_size: f64,
|
|
|
pixel_size: i32,
|
|
|
style_hint: FontStyleHint,
|
|
|
weight: i32,
|
|
|
style: FontStyle,
|
|
|
underline: bool,
|
|
|
strike_out: bool,
|
|
|
fixed_pitch: bool,
|
|
|
style_name: Option<String>,
|
|
|
}
|
|
|
|
|
|
impl Default for FontInfo {
|
|
|
fn default() -> Self {
|
|
|
//Might want to revisit these defaults
|
|
|
//but I'll put something in for now
|
|
|
Self {
|
|
|
family: "宋体".into(),
|
|
|
// 字号12?
|
|
|
point_size: 12.0,
|
|
|
pixel_size: i32::default(),
|
|
|
style_hint: FontStyleHint::Helvetica,
|
|
|
weight: i32::default(),
|
|
|
style: FontStyle::Normal,
|
|
|
underline: false,
|
|
|
strike_out: false,
|
|
|
fixed_pitch: false,
|
|
|
style_name: None,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Display for FontInfo {
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
write!(
|
|
|
f,
|
|
|
"{},{},{},{},{},{},{},{},{},0{}",
|
|
|
self.family,
|
|
|
self.point_size.round(),
|
|
|
self.pixel_size,
|
|
|
Into::<i32>::into(&self.style_hint),
|
|
|
self.weight,
|
|
|
Into::<i32>::into(&self.style),
|
|
|
i32::from(self.underline),
|
|
|
i32::from(self.strike_out),
|
|
|
i32::from(self.fixed_pitch),
|
|
|
if let Some(sn) = &self.style_name {
|
|
|
format!(",{sn}")
|
|
|
} else {
|
|
|
String::new()
|
|
|
},
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
enum TextEntity<'a> {
|
|
|
Text(&'a dxf::entities::Text),
|
|
|
MText(&'a dxf::entities::MText),
|
|
|
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
|
|
|
} |