导入端子适配

qdj
邱德佳 2 weeks ago
parent 8b20b2316b
commit c851c6b5a3

@ -1,4 +1,4 @@
use dxf::entities::{AttributeDefinition, Entity, EntityType};
use dxf::entities::{AttributeDefinition, Entity, EntityType, Insert};
use dxf::entities::{LwPolyline, Polyline};
use dxf::enums::{AttachmentPoint, HorizontalTextJustification, Units, VerticalTextJustification};
use dxf::{Block, Drawing};
@ -187,6 +187,23 @@ fn qet_insert_rotation(rotation: f64) -> f64 {
-rotation
}
fn connector_attribute(insert: &Insert) -> Option<&str> {
insert
.attributes()
.find(|attribute| {
attribute
.attribute_tag
.trim()
.eq_ignore_ascii_case("connector")
})
.map(|attribute| attribute.value.trim())
.filter(|value| !value.is_empty())
}
fn is_arrowunknown_terminal(insert: &Insert) -> bool {
insert.name.trim().eq_ignore_ascii_case("ARROWUNKNOWN") && connector_attribute(insert).is_some()
}
// 用于判断多边形是否接近圆形的特征,有点类似函数声明
trait Circularity {
// 判断图形是否为圆形
@ -584,7 +601,7 @@ pub(crate) enum Objects {
DynamicText(DynamicText),
Text(Text),
Line(Line),
//Terminal(Terminal),
Terminal(Terminal),
Group(Vec<Objects>),
BlockInstance(BlockInstance),
}
@ -620,6 +637,10 @@ impl Objects {
line.x2 += offset_x;
line.y2 += offset_y;
}
Objects::Terminal(terminal) => {
terminal.x += offset_x;
terminal.y += offset_y;
}
Objects::Group(objects) => {
for object in objects {
object.translate(offset_x, offset_y);
@ -662,6 +683,13 @@ impl Objects {
line.x2 = x2;
line.y2 = y2;
}
Objects::Terminal(terminal) => {
let (x, y) =
rotate_point_around(terminal.x, terminal.y, pivot_x, pivot_y, angle_deg);
terminal.x = x;
terminal.y = y;
terminal.orientation = terminal.orientation.rotated(angle_deg);
}
Objects::Group(objects) => {
rotate_objects_around(objects, pivot_x, pivot_y, angle_deg);
}
@ -763,6 +791,7 @@ impl ScaleEntity for Objects {
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::Terminal(terminal) => terminal.scale(fact_x, fact_y),
Objects::Group(vec) => vec.iter_mut().for_each(|ob| ob.scale(fact_x, fact_y)),
Objects::BlockInstance(block) => block
.objects
@ -780,6 +809,7 @@ impl ScaleEntity for Objects {
Objects::DynamicText(dynamic_text) => dynamic_text.left_bound(),
Objects::Text(text) => text.left_bound(),
Objects::Line(line) => line.left_bound(),
Objects::Terminal(terminal) => terminal.x,
Objects::Group(vec) => {
let lb = vec.iter().min_by(|ob1, ob2| {
ob1.left_bound()
@ -806,6 +836,7 @@ impl ScaleEntity for Objects {
Objects::DynamicText(dynamic_text) => dynamic_text.right_bound(),
Objects::Text(text) => text.right_bound(),
Objects::Line(line) => line.right_bound(),
Objects::Terminal(terminal) => terminal.x,
Objects::Group(vec) => {
let rb = vec.iter().max_by(|ob1, ob2| {
ob1.right_bound()
@ -831,6 +862,7 @@ impl ScaleEntity for Objects {
Objects::DynamicText(dynamic_text) => dynamic_text.top_bound(),
Objects::Text(text) => text.top_bound(),
Objects::Line(line) => line.top_bound(),
Objects::Terminal(terminal) => terminal.y,
Objects::Group(vec) => {
let tb = vec.iter().min_by(|ob1, ob2| {
ob1.top_bound()
@ -856,6 +888,7 @@ impl ScaleEntity for Objects {
Objects::DynamicText(dynamic_text) => dynamic_text.bot_bound(),
Objects::Text(text) => text.bot_bound(),
Objects::Line(line) => line.bot_bound(),
Objects::Terminal(terminal) => terminal.y,
Objects::Group(vec) => {
let bb = vec.iter().max_by(|ob1, ob2| {
ob1.bot_bound()
@ -1327,6 +1360,15 @@ impl<'a> ObjectsBuilder<'a> {
Ok(Objects::Polygon(poly))
}
EntityType::Insert(ins) => {
if is_arrowunknown_terminal(ins) {
if let Some(mut terminal) = Terminal::from_insert(ins) {
terminal.scale(self.scale_fact.x, self.scale_fact.y);
terminal.x += self.offset.x;
terminal.y -= self.offset.y;
return Ok(Objects::Terminal(terminal));
}
}
//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 {
@ -1435,6 +1477,7 @@ impl From<&Objects> for Either<XMLElement, Vec<XMLElement>> {
Objects::DynamicText(dtext) => Either::Left(dtext.into()),
Objects::Text(txt) => Either::Left(txt.into()),
Objects::Line(line) => Either::Left(line.into()),
Objects::Terminal(terminal) => Either::Left(terminal.into()),
Objects::Group(block) => Either::Right(
// 迭代组中的每个对象try_from转换,filter_map和ok忽略转换失败对象collect()收集转换成功元素
block
@ -1466,6 +1509,7 @@ impl TryFrom<&Objects> for XMLElement {
Objects::DynamicText(dtext) => Ok(dtext.into()),
Objects::Text(txt) => Ok(txt.into()),
Objects::Line(line) => Ok(line.into()),
Objects::Terminal(terminal) => Ok(terminal.into()),
Objects::Group(_) => Err("Unsupported"),
Objects::BlockInstance(_) => Err("Unsupported"),
}
@ -1655,6 +1699,10 @@ impl From<(&Drawing, u32)> for Description {
.filter_map(|ent| match &ent.specific {
EntityType::Insert(ins) => {
// 检查Insert类型entity的is_visible属性
if is_arrowunknown_terminal(ins) {
return ObjectsBuilder::new(ent, spline_step).build().ok();
}
if !ent.common.is_visible {
return None;
}
@ -1711,18 +1759,128 @@ impl From<(&Drawing, u32)> for Description {
}
}
//probably don't need to worry about this as they won't exist in the dxf...
/*pub struct Terminal {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TermOrientation {
North,
East,
South,
West,
}
impl TermOrientation {
fn vector(self) -> (f64, f64) {
match self {
Self::North => (0.0, -1.0),
Self::East => (1.0, 0.0),
Self::South => (0.0, 1.0),
Self::West => (-1.0, 0.0),
}
}
fn from_vector(x: f64, y: f64) -> Self {
if x.abs() >= y.abs() {
if x >= 0.0 {
Self::East
} else {
Self::West
}
} else if y >= 0.0 {
Self::South
} else {
Self::North
}
}
fn rotated(self, angle_deg: f64) -> Self {
let angle = angle_deg.to_radians();
let (x, y) = self.vector();
Self::from_vector(
x * angle.cos() - y * angle.sin(),
x * angle.sin() + y * angle.cos(),
)
}
}
impl Display for TermOrientation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::North => "n",
Self::East => "e",
Self::South => "s",
Self::West => "w",
})
}
}
#[derive(Debug)]
pub struct Terminal {
x: f64,
y: f64,
uuid: Uuid,
name: String,
orientation: TermOrient,
//type?
// Generic
// Indoor Terminal Block
// External Terminal Block
}*/
connector: String,
orientation: TermOrientation,
}
impl Terminal {
fn from_insert(insert: &Insert) -> Option<Self> {
let connector = connector_attribute(insert)?.to_owned();
Some(Self {
x: insert.location.x,
y: -insert.location.y,
uuid: Uuid::new_v4(),
name: connector.clone(),
connector,
// SWE 的通用连接点块以东向为零角度DXF 到 QET 时 Y 轴翻转。
orientation: TermOrientation::East.rotated(qet_insert_rotation(insert.rotation)),
})
}
}
impl ScaleEntity for Terminal {
fn scale(&mut self, fact_x: f64, fact_y: f64) {
self.x *= fact_x;
self.y *= fact_y;
let (dir_x, dir_y) = self.orientation.vector();
if fact_x.abs() > f64::EPSILON || fact_y.abs() > f64::EPSILON {
self.orientation = TermOrientation::from_vector(dir_x * fact_x, dir_y * fact_y);
}
}
fn left_bound(&self) -> f64 {
self.x
}
fn right_bound(&self) -> f64 {
self.x
}
fn top_bound(&self) -> f64 {
self.y
}
fn bot_bound(&self) -> f64 {
self.y
}
}
impl From<&Terminal> for XMLElement {
fn from(terminal: &Terminal) -> Self {
let mut terminal_xml = XMLElement::new("terminal");
terminal_xml.add_attribute("x", two_dec(terminal.x));
terminal_xml.add_attribute("y", two_dec(terminal.y));
terminal_xml.add_attribute("uuid", format!("{{{}}}", terminal.uuid));
terminal_xml.add_attribute("name", &terminal.name);
terminal_xml.add_attribute("orientation", &terminal.orientation);
terminal_xml.add_attribute("type", "Generic");
let mut connection_point = XMLElement::new("connection_point");
connection_point.add_attribute("tag", &terminal.connector);
terminal_xml.add_child(connection_point);
terminal_xml
}
}
#[derive(Debug)]
pub struct Names {
@ -2434,7 +2592,64 @@ pub(crate) fn strip_mtext_control_sequences(input: &str) -> String {
#[cfg(test)]
mod tests {
use super::{BlockInstance, BlockMetadata, Description, Line, Objects, ScaleEntity};
use super::{
BlockInstance, BlockMetadata, Definition, Description, Line, Objects, ObjectsBuilder,
ScaleEntity,
};
use dxf::entities::{Attribute, Entity, EntityType, Insert};
use dxf::{Drawing, Point};
fn connector_insert(rotation: f64) -> Insert {
let mut insert = Insert {
name: "ARROWUNKNOWN".into(),
location: Point::new(12.0, 34.0, 0.0),
rotation,
..Default::default()
};
let mut drawing = Drawing::new();
insert.add_attribute(
&mut drawing,
Attribute {
attribute_tag: "connector".into(),
value: "N:0-C:0".into(),
..Default::default()
},
);
insert
}
#[test]
fn arrowunknown_connector_becomes_qet_terminal() {
let entity = Entity::new(EntityType::Insert(connector_insert(90.0)));
let object = ObjectsBuilder::new(&entity, 20)
.build()
.expect("ARROWUNKNOWN with connector should be supported");
let xml = simple_xml_builder::XMLElement::try_from(&object)
.expect("terminal should serialize")
.to_string();
assert!(xml.contains("<terminal "), "unexpected XML: {xml}");
assert!(xml.contains("x=\"12\""));
assert!(xml.contains("y=\"-34\""));
assert!(xml.contains("name=\"N:0-C:0\""));
assert!(xml.contains("orientation=\"n\""));
assert!(xml.contains("type=\"Generic\""));
assert!(xml.contains("<connection_point tag=\"N:0-C:0\""));
}
#[test]
fn drawing_conversion_keeps_arrowunknown_terminal_semantics() {
let mut drawing = Drawing::new();
drawing.add_entity(Entity::new(EntityType::Insert(connector_insert(0.0))));
let definition = Definition::new("terminal", 20, &drawing);
let xml = simple_xml_builder::XMLElement::from(&definition).to_string();
assert!(xml.contains("<terminal "));
assert!(xml.contains("name=\"N:0-C:0\""));
assert!(xml.contains("orientation=\"e\""));
assert!(xml.contains("<connection_point tag=\"N:0-C:0\""));
}
#[test]
fn description_bounds_use_outer_edges_for_single_group() {

Loading…
Cancel
Save