1# Copyright (c) 2019-2020 Manfred Moitzi 2# License: MIT License 3from typing import TYPE_CHECKING, List, Iterable 4import copy 5import logging 6from ezdxf.lldxf import validator 7from ezdxf.lldxf.attributes import ( 8 DXFAttr, DXFAttributes, DefSubclass, XType, RETURN_DEFAULT, 9 group_code_mapping, 10) 11from ezdxf.lldxf.tags import Tags, DXFTag 12from ezdxf.lldxf.const import SUBCLASS_MARKER, DXF2000 13from ezdxf.math import Vec3, X_AXIS, Z_AXIS, NULLVEC 14from ezdxf.math.transformtools import transform_extrusion 15from ezdxf.explode import explode_entity 16from ezdxf.audit import AuditError 17from .dxfentity import base_class, SubclassProcessor 18from .dxfgfx import DXFGraphic, acdb_entity 19from .factory import register_entity 20from .dimension import OverrideMixin 21 22logger = logging.getLogger('ezdxf') 23 24if TYPE_CHECKING: 25 from ezdxf.eztypes import ( 26 TagWriter, DXFNamespace, Vertex, Matrix44, BaseLayout, EntityQuery, 27 Auditor, 28 ) 29 30__all__ = ['Leader'] 31 32acdb_leader = DefSubclass('AcDbLeader', { 33 'dimstyle': DXFAttr( 34 3, default='Standard', 35 validator=validator.is_valid_table_name, 36 # no fixer! 37 ), 38 39 # Arrowhead flag: 0/1 = no/yes 40 'has_arrowhead': DXFAttr( 41 71, default=1, optional=True, 42 validator=validator.is_integer_bool, 43 fixer=RETURN_DEFAULT, 44 ), 45 46 # Leader path type: 47 # 0 = Straight line segments 48 # 1 = Spline 49 'path_type': DXFAttr( 50 72, default=0, optional=True, 51 validator=validator.is_integer_bool, 52 fixer=RETURN_DEFAULT, 53 ), 54 55 # Annotation type or leader creation flag: 56 # 0 = Created with text annotation 57 # 1 = Created with tolerance annotation; 58 # 2 = Created with block reference annotation 59 # 3 = Created without any annotation 60 'annotation_type': DXFAttr( 61 73, default=3, 62 validator=validator.is_in_integer_range(0, 4), 63 fixer=RETURN_DEFAULT, 64 ), 65 66 # Hook line direction flag: 67 # 1 = Hook line (or end of tangent for a spline leader) is the opposite 68 # direction from the horizontal vector 69 # 0 = Hook line (or end of tangent for a spline leader) is the same 70 # direction as horizontal vector (see code 75) 71 # DXF reference error: swapped meaning of 1/0 72 'hookline_direction': DXFAttr( 73 74, default=1, optional=True, 74 validator=validator.is_integer_bool, 75 fixer=RETURN_DEFAULT, 76 ), 77 78 # Hook line flag: 0/1 = no/yes 79 'has_hookline': DXFAttr( 80 75, default=1, optional=True, 81 validator=validator.is_integer_bool, 82 fixer=RETURN_DEFAULT, 83 ), 84 85 # Text annotation height: 86 'text_height': DXFAttr( 87 40, default=1, optional=True, 88 validator=validator.is_greater_zero, 89 fixer=RETURN_DEFAULT, 90 ), 91 92 # Text annotation width: 93 'text_width': DXFAttr( 94 41, default=1, optional=True, 95 validator=validator.is_greater_zero, 96 fixer=RETURN_DEFAULT, 97 ), 98 99 # 76: Number of vertices in leader (ignored for OPEN) 100 # 10, 20, 30: Vertex coordinates (one entry for each vertex) 101 102 # Color to use if leader's DIMCLRD = BYBLOCK 103 'block_color': DXFAttr( 104 77, default=7, optional=True, 105 validator=validator.is_valid_aci_color, 106 fixer=RETURN_DEFAULT, 107 ), 108 109 # Hard reference to associated annotation: 110 # (mtext, tolerance, or insert entity) 111 'annotation_handle': DXFAttr(340, default='0', optional=True), 112 113 'normal_vector': DXFAttr( 114 210, xtype=XType.point3d, default=Z_AXIS, optional=True, 115 validator=validator.is_not_null_vector, 116 fixer=RETURN_DEFAULT, 117 ), 118 119 # 'horizontal' direction for leader 120 'horizontal_direction': DXFAttr( 121 211, xtype=XType.point3d, default=X_AXIS, optional=True, 122 validator=validator.is_not_null_vector, 123 fixer=RETURN_DEFAULT, 124 ), 125 126 # Offset of last leader vertex from block reference insertion point 127 'leader_offset_block_ref': DXFAttr( 128 212, xtype=XType.point3d, default=NULLVEC, optional=True), 129 130 # Offset of last leader vertex from annotation placement point 131 'leader_offset_annotation_placement': DXFAttr( 132 213, xtype=XType.point3d, default=NULLVEC, optional=True), 133 134 # Xdata belonging to the application ID "ACAD" follows a leader entity if 135 # any dimension overrides have been applied to this entity. See Dimension 136 # Style Overrides. 137}) 138acdb_leader_group_codes = group_code_mapping(acdb_leader) 139 140 141@register_entity 142class Leader(DXFGraphic, OverrideMixin): 143 """ DXF LEADER entity """ 144 DXFTYPE = 'LEADER' 145 DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_leader) 146 MIN_DXF_VERSION_FOR_EXPORT = DXF2000 147 148 def __init__(self): 149 super().__init__() 150 self.vertices: List[Vec3] = [] 151 152 def _copy_data(self, entity: 'Leader') -> None: 153 """ Copy vertices. """ 154 entity.vertices = copy.deepcopy(self.vertices) 155 156 def load_dxf_attribs( 157 self, processor: SubclassProcessor = None) -> 'DXFNamespace': 158 dxf = super().load_dxf_attribs(processor) 159 if processor: 160 tags = Tags(self.load_vertices(processor.subclass_by_index(2))) 161 processor.fast_load_dxfattribs( 162 dxf, acdb_leader_group_codes, tags, recover=True) 163 return dxf 164 165 def load_vertices(self, tags: Tags) -> Iterable[DXFTag]: 166 for tag in tags: 167 if tag.code == 10: 168 self.vertices.append(tag.value) 169 elif tag.code == 76: 170 # Number of vertices in leader (ignored for OPEN) 171 pass 172 else: 173 yield tag 174 175 def preprocess_export(self, tagwriter: 'TagWriter') -> bool: 176 if len(self.vertices) < 2: 177 logger.debug(f"Invalid {str(self)}: more than 1 vertex required.") 178 return False 179 else: 180 return True 181 182 def export_entity(self, tagwriter: 'TagWriter') -> None: 183 """ Export entity specific data as DXF tags. """ 184 super().export_entity(tagwriter) 185 tagwriter.write_tag2(SUBCLASS_MARKER, acdb_leader.name) 186 self.dxf.export_dxf_attribs(tagwriter, [ 187 'dimstyle', 'has_arrowhead', 'path_type', 'annotation_type', 188 'hookline_direction', 'has_hookline', 'text_height', 'text_width', 189 ]) 190 self.export_vertices(tagwriter) 191 self.dxf.export_dxf_attribs(tagwriter, [ 192 'block_color', 'annotation_handle', 'normal_vector', 193 'horizontal_direction', 'leader_offset_block_ref', 194 'leader_offset_annotation_placement' 195 ]) 196 197 def export_vertices(self, tagwriter: 'TagWriter') -> None: 198 tagwriter.write_tag2(76, len(self.vertices)) 199 for vertex in self.vertices: 200 tagwriter.write_vertex(10, vertex) 201 202 def set_vertices(self, vertices: Iterable['Vertex']): 203 """ Set vertices of the leader, vertices is an iterable of 204 (x, y [,z]) tuples or :class:`~ezdxf.math.Vec3`. 205 206 """ 207 self.vertices = [Vec3(v) for v in vertices] 208 209 def transform(self, m: 'Matrix44') -> 'Leader': 210 """ Transform LEADER entity by transformation matrix `m` inplace. """ 211 self.vertices = list(m.transform_vertices(self.vertices)) 212 self.dxf.normal_vector, _ = transform_extrusion(self.dxf.normal_vector, 213 m) # ??? 214 self.dxf.horizontal_direction = m.transform_direction( 215 self.dxf.horizontal_direction) 216 return self 217 218 def virtual_entities(self) -> Iterable['DXFGraphic']: 219 """ 220 Yields 'virtual' parts of LEADER as DXF primitives. 221 222 This entities are located at the original positions, but are not stored 223 in the entity database, have no handle and are not assigned to any 224 layout. 225 226 .. versionadded:: 0.14 227 228 """ 229 from ezdxf.render.leader import virtual_entities 230 return virtual_entities(self) 231 232 def explode(self, target_layout: 'BaseLayout' = None) -> 'EntityQuery': 233 """ 234 Explode parts of LEADER as DXF primitives into target layout, if target 235 layout is ``None``, the target layout is the layout of the LEADER. 236 237 Returns an :class:`~ezdxf.query.EntityQuery` container with all 238 DXF parts. 239 240 Args: 241 target_layout: target layout for DXF parts, ``None`` for same 242 layout as source entity. 243 244 .. versionadded:: 0.14 245 246 """ 247 return explode_entity(self, target_layout) 248 249 def audit(self, auditor: 'Auditor') -> None: 250 """ Validity check. """ 251 super().audit(auditor) 252 if len(self.vertices) < 2: 253 auditor.fixed_error( 254 code=AuditError.INVALID_VERTEX_COUNT, 255 message=f'Deleted entity {str(self)} with invalid vertex count ' 256 f'= {len(self.vertices)}.', 257 dxf_entity=self, 258 ) 259 self.destroy() 260