|
|
|
|
@ -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() {
|
|
|
|
|
|