@ -654,6 +654,27 @@ impl Objects {
}
}
/// 仅给可描边图元写入 DXF 的数值线宽;文本和端子没有描边线宽。
fn apply_line_width_mm ( & mut self , line_width_mm : f64 ) {
match self {
Objects ::Arc ( arc ) = > arc . update_line_width_mm ( line_width_mm ) ,
Objects ::Ellipse ( ellipse ) = > ellipse . update_line_width_mm ( line_width_mm ) ,
Objects ::Polygon ( polygon ) = > polygon . update_line_width_mm ( line_width_mm ) ,
Objects ::Line ( line ) = > line . update_line_width_mm ( line_width_mm ) ,
Objects ::Group ( objects ) = > {
for object in objects {
object . apply_line_width_mm ( line_width_mm ) ;
}
}
Objects ::BlockInstance ( block ) = > {
for object in & mut block . objects {
object . apply_line_width_mm ( line_width_mm ) ;
}
}
Objects ::DynamicText ( _ ) | Objects ::Text ( _ ) | Objects ::Terminal ( _ ) = > { }
}
}
fn rotate_around ( & mut self , pivot_x : f64 , pivot_y : f64 , angle_deg : f64 ) {
match self {
Objects ::Arc ( arc ) = > arc . rotate_around ( pivot_x , pivot_y , angle_deg ) ,
@ -1051,6 +1072,9 @@ impl<'a> ObjectsBuilder<'a> {
// 获取线型名称
let line_type_name : String = self . ent . common . line_type_name . clone ( ) ;
// DXF 组码 370 以 1/100 mm 保存数值线宽;负值表示 BYLAYER/BYBLOCK 等继承方式,
// 当前没有解析到明确数值时不生成 line-width-mm。
let entity_line_width_mm = dxf_line_weight_mm ( self . ent . common . lineweight_enum_value ) ;
// 通用样式修改函数: 根据line_type_name修改现有style中的line-style
let update_line_style = | style : & mut String | {
@ -1083,7 +1107,7 @@ impl<'a> ObjectsBuilder<'a> {
} ;
// 实体类型转换处理
match & self . ent . specific {
let result = match & self . ent . specific {
// Circle → Ellipse :圆转换为椭圆
EntityType ::Circle ( circle ) = > {
let mut ellipse : Ellipse = circle . into ( ) ;
@ -1298,10 +1322,10 @@ impl<'a> ObjectsBuilder<'a> {
} else {
HexColor ::from_u32 ( self . ent . common . color_24_bit as u32 )
} ;
// === 获取weight ===
let mut l w: Option < i16 > = None ;
// LW 直接读取毫米数值 line-width-mm, 不能再压缩成 thin/normal 等旧档位。
let mut l ine_width_mm = entity_line_width_mm ;
if lwpolyline . constant_width > 0.0 {
l w = Some ( ( lwpolyline . constant_width * 100.0 ) as i16 ) ;
l ine_ width_mm = Some ( lwpolyline . constant_width ) ;
} else if let Some ( v ) = lwpolyline . vertices . get ( 0 ) {
let w = if v . starting_width > 0.0 {
v . starting_width
@ -1309,10 +1333,9 @@ impl<'a> ObjectsBuilder<'a> {
v . ending_width
} ;
if w > 0.0 {
l w = Some ( ( w * 100.0 ) as i16 ) ;
l ine_ width_mm = Some ( w ) ;
}
}
// ===============================
match lwpolyline . vertices . len ( ) {
0 | 1 = > Err ( "Error empty LwPolyline" ) ,
@ -1321,9 +1344,8 @@ impl<'a> ObjectsBuilder<'a> {
match Arc ::try_from_lwpolyline_with_color ( lwpolyline , color ) {
Ok ( mut arc ) = > {
arc . update_line_style ( & update_line_style ) ;
// 更新线条粗细
if let Some ( w ) = lw {
arc . update_line_weight ( w ) ;
if let Some ( width_mm ) = line_width_mm {
arc . update_line_width_mm ( width_mm ) ;
}
arc . scale ( self . scale_fact . x , self . scale_fact . y ) ;
arc . x + = self . offset . x ;
@ -1340,6 +1362,9 @@ impl<'a> ObjectsBuilder<'a> {
// 根据line_type_name更新线型样式
line . update_line_style ( & update_line_style ) ;
if let Some ( width_mm ) = line_width_mm {
line . update_line_width_mm ( width_mm ) ;
}
line . scale ( self . scale_fact . x , self . scale_fact . y ) ;
line . x1 + = self . offset . x ;
@ -1356,6 +1381,9 @@ impl<'a> ObjectsBuilder<'a> {
{
// 根据line_type_name更新线型样式
ellipse . update_line_style ( & update_line_style ) ;
if let Some ( width_mm ) = line_width_mm {
ellipse . update_line_width_mm ( width_mm ) ;
}
ellipse . scale ( self . scale_fact . x , self . scale_fact . y ) ;
ellipse . x + = self . offset . x ;
@ -1373,12 +1401,15 @@ impl<'a> ObjectsBuilder<'a> {
match obj {
Objects ::Arc ( arc ) = > {
arc . update_line_style ( & update_line_style ) ;
if let Some ( w ) = l w {
arc . update_line_w eight( w ) ;
if let Some ( w idth_mm ) = l ine_ width_mm {
arc . update_line_w idth_mm( width_mm ) ;
}
}
Objects ::Line ( line ) = > {
line . update_line_style ( & update_line_style ) ;
if let Some ( width_mm ) = line_width_mm {
line . update_line_width_mm ( width_mm ) ;
}
}
_ = > { }
}
@ -1393,6 +1424,9 @@ impl<'a> ObjectsBuilder<'a> {
// 根据line_type_name更新线型样式
poly . update_line_style ( & update_line_style ) ;
if let Some ( width_mm ) = line_width_mm {
poly . update_line_width_mm ( width_mm ) ;
}
poly . scale ( self . scale_fact . x , self . scale_fact . y ) ;
for cord in & mut poly . coordinates {
@ -1418,6 +1452,18 @@ impl<'a> ObjectsBuilder<'a> {
Ok ( Objects ::Polygon ( poly ) )
}
EntityType ::Trace ( trace ) = > {
let mut poly : Polygon = trace . 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 ) = > {
if is_arrowunknown_terminal ( ins ) {
if let Some ( mut terminal ) = Terminal ::from_insert ( ins ) {
@ -1520,7 +1566,20 @@ impl<'a> ObjectsBuilder<'a> {
//dbg!(&self.ent.specific);
Err ( "Need to implement the rest of the entity types" )
}
} ;
let mut object = result ? ;
// LWPOLYLINE 已优先使用 constant_width/顶点宽度; INSERT 内的子图元各自读取线宽。
if ! matches! (
& self . ent . specific ,
EntityType ::LwPolyline ( _ ) | EntityType ::Insert ( _ )
) {
if let Some ( line_width_mm ) = entity_line_width_mm {
object . apply_line_width_mm ( line_width_mm ) ;
}
}
Ok ( object )
}
}
@ -2257,6 +2316,54 @@ pub fn two_dec(num: f64) -> f64 {
( num * 100.0 ) . round ( ) / 100.0
}
// 与 LW LineWidthSettings::options() 保持一致。首项是“默认 0.25 mm”,
// 后面仍包含可显式选择的 0.25 mm; 保留这个顺序才能复现等距时的选择结果。
const LW_LINE_WIDTH_PRESETS_MM : [ f64 ; 25 ] = [
0.25 , 0.00 , 0.05 , 0.09 , 0.13 , 0.15 , 0.18 , 0.20 , 0.25 , 0.30 , 0.35 , 0.40 , 0.50 , 0.53 , 0.60 , 0.70 ,
0.80 , 0.90 , 1.00 , 1.06 , 1.20 , 1.40 , 1.58 , 2.00 , 2.11 ,
] ;
/// 按 LW 的规则选择距离输入值最近的预设线宽;距离相同时保留列表中靠前的值。
fn normalize_lw_line_width_mm ( line_width_mm : f64 ) -> f64 {
let mut best = LW_LINE_WIDTH_PRESETS_MM [ 0 ] ;
let mut best_distance = ( line_width_mm - best ) . abs ( ) ;
for preset in LW_LINE_WIDTH_PRESETS_MM . iter ( ) . copied ( ) . skip ( 1 ) {
let distance = ( line_width_mm - preset ) . abs ( ) ;
if distance < best_distance {
best = preset ;
best_distance = distance ;
}
}
best
}
/// LW 使用 line-width-mm 保存毫米线宽,不再输出旧的 line-weight 档位字段。
/// 删除 style 中已有的两个线宽字段后,再写入归一化后的 LW 预设值。
fn update_style_line_width_mm ( style : & mut String , line_width_mm : f64 ) {
let mut properties = style
. split ( ';' )
. map ( str ::trim )
. filter ( | part | {
! part . is_empty ( )
& & ! part . starts_with ( "line-weight:" )
& & ! part . starts_with ( "line-width-mm:" )
} )
. map ( str ::to_string )
. collect ::< Vec < _ > > ( ) ;
let normalized = normalize_lw_line_width_mm ( line_width_mm ) ;
properties . push ( format! ( "line-width-mm:{normalized:.2}" ) ) ;
* style = properties . join ( ";" ) ;
}
/// DXF 实体公共组码 370 的非负值单位为 1/100 mm。
/// BYLAYER、BYBLOCK、DEFAULT 等负值不是具体线宽,不能伪造成固定值。
fn dxf_line_weight_mm ( raw_value : i16 ) -> Option < f64 > {
( raw_value > = 0 ) . then ( | | f64 ::from ( raw_value ) / 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?
@ -2652,11 +2759,13 @@ pub(crate) fn strip_mtext_control_sequences(input: &str) -> String {
#[ cfg(test) ]
mod tests {
use super ::{
xml_elements_for_object , BlockInstance , BlockMetadata , Definition , Description , Line ,
Objects , ObjectsBuilder , ScaleEntity ,
dxf_line_weight_mm , normalize_lw_line_width_mm , xml_elements_for_object , BlockInstance ,
BlockMetadata , Definition , Description , Line , Objects , ObjectsBuilder , ScaleEntity ,
} ;
use dxf ::entities ::{
Attribute , Entity , EntityType , Insert , Line as DxfLine , LwPolyline , Solid ,
} ;
use dxf ::entities ::{ Attribute , Entity , EntityType , Insert , LwPolyline } ;
use dxf ::{ Drawing , LwPolylineVertex , Point } ;
use dxf ::{ Block , Drawing , LwPolylineVertex , Point } ;
fn connector_insert ( rotation : f64 ) -> Insert {
let mut insert = Insert {
@ -2710,6 +2819,35 @@ mod tests {
assert! ( xml . contains ( "<connection_point tag=\"N:0-C:0\"" ) ) ;
}
#[ test ]
fn solid_inside_block_is_exported_as_filled_polygon ( ) {
let solid = Solid ::new (
Point ::new ( 0.0 , 0.0 , 0.0 ) ,
Point ::new ( 10.0 , 0.0 , 0.0 ) ,
Point ::new ( 0.0 , 5.0 , 0.0 ) ,
Point ::new ( 10.0 , 5.0 , 0.0 ) ,
) ;
let mut drawing = Drawing ::new ( ) ;
drawing . add_block ( Block {
name : "FILLED_BLOCK" . into ( ) ,
entities : vec ! [ Entity ::new ( EntityType ::Solid ( solid ) ) ] ,
.. Default ::default ( )
} ) ;
drawing . add_entity ( Entity ::new ( EntityType ::Insert ( Insert {
name : "FILLED_BLOCK" . into ( ) ,
.. Default ::default ( )
} ) ) ) ;
let definition = Definition ::new ( "filled-block" , 20 , & drawing ) ;
let xml = simple_xml_builder ::XMLElement ::from ( & definition ) . to_string ( ) ;
assert! ( xml . contains ( "filling:black" ) , "unexpected XML: {xml}" ) ;
assert! (
xml . contains ( "block_name=\"FILLED_BLOCK\"" ) ,
"unexpected XML: {xml}"
) ;
}
#[ test ]
fn mixed_lwpolyline_keeps_line_and_negative_bulge_arc ( ) {
let polyline = LwPolyline {
@ -2751,6 +2889,49 @@ mod tests {
assert! ( xml . contains ( "height=\"5\"" ) , "unexpected XML: {xml}" ) ;
assert! ( xml . contains ( "start=\"0\"" ) , "unexpected XML: {xml}" ) ;
assert! ( xml . contains ( "angle=\"180\"" ) , "unexpected XML: {xml}" ) ;
assert_eq! (
2 ,
xml . matches ( "line-width-mm:0.35" ) . count ( ) ,
"直线和圆弧都应继承 constant_width: {xml}"
) ;
assert! (
! xml . contains ( "line-weight:" ) ,
"数值线宽不应再携带旧的 line-weight 档位: {xml}"
) ;
}
#[ test ]
fn dxf_numeric_lineweight_is_used_and_thickness_is_ignored ( ) {
let mut entity = Entity ::new ( EntityType ::Line ( DxfLine {
thickness : 99.0 ,
p1 : Point ::new ( 0.0 , 0.0 , 0.0 ) ,
p2 : Point ::new ( 10.0 , 0.0 , 0.0 ) ,
.. Default ::default ( )
} ) ) ;
entity . common . lineweight_enum_value = 35 ;
let object = ObjectsBuilder ::new ( & entity , 20 )
. build ( )
. expect ( "DXF LINE should be converted" ) ;
let xml = xml_elements_for_object ( & object , None )
. into_iter ( )
. map ( | element | element . to_string ( ) )
. collect ::< Vec < _ > > ( )
. join ( "" ) ;
assert! ( xml . contains ( "line-width-mm:0.35" ) , "unexpected XML: {xml}" ) ;
assert! ( ! xml . contains ( "line-weight:" ) , "unexpected XML: {xml}" ) ;
assert! ( ! xml . contains ( "line-width-mm:0.25" ) , "unexpected XML: {xml}" ) ;
assert_eq! ( None , dxf_line_weight_mm ( - 1 ) ) ;
assert_eq! ( None , dxf_line_weight_mm ( - 2 ) ) ;
}
#[ test ]
fn line_width_is_normalized_to_the_nearest_lw_preset ( ) {
assert_eq! ( 0.35 , normalize_lw_line_width_mm ( 0.33 ) ) ;
assert_eq! ( 0.70 , normalize_lw_line_width_mm ( 0.68 ) ) ;
assert_eq! ( 0.25 , normalize_lw_line_width_mm ( 0.27 ) ) ;
assert_eq! ( 2.11 , normalize_lw_line_width_mm ( 9.99 ) ) ;
}
#[ test ]
@ -2761,7 +2942,7 @@ mod tests {
- 8.0 ,
42.0 ,
16.0 ,
"line-style:normal;line-w eight:thin ;filling:none;color:black". into ( ) ,
"line-style:normal;line-w idth-mm:0.00 ;filling:none;color:black". into ( ) ,
) ) ] ) ] ,
} ;
@ -2779,7 +2960,7 @@ mod tests {
- 2_070.0 ,
1_330.0 ,
- 2_050.0 ,
"line-style:normal;line-w eight:thin ;filling:none;color:black". into ( ) ,
"line-style:normal;line-w idth-mm:0.00 ;filling:none;color:black". into ( ) ,
) ) ] ) ] ,
} ;
@ -2804,7 +2985,7 @@ mod tests {
0.0 ,
10.0 ,
0.0 ,
"line-style:normal;line-w eight:thin ;filling:none;color:black". into ( ) ,
"line-style:normal;line-w idth-mm:0.00 ;filling:none;color:black". into ( ) ,
) ) ] ,
} ) ] ,
} ;
@ -2822,7 +3003,7 @@ mod tests {
0.0 ,
20.0 ,
0.0 ,
"line-style:normal;line-w eight:thin ;filling:none;color:black". into ( ) ,
"line-style:normal;line-w idth-mm:0.00 ;filling:none;color:black". into ( ) ,
) ) ] ;
super ::rotate_objects_around ( & mut objects , 10.0 , 0.0 , super ::qet_insert_rotation ( 90.0 ) ) ;
@ -2855,7 +3036,7 @@ mod tests {
0.0 ,
10.0 ,
0.0 ,
"line-style:normal;line-w eight:thin ;filling:none;color:black". into ( ) ,
"line-style:normal;line-w idth-mm:0.00 ;filling:none;color:black". into ( ) ,
) ) ] ,
} ) ] ,
} ) ] ,