Files
stepanalyser/.venv/lib/python3.12/site-packages/ezdxf/entities/leader.py
Christian Anetzberger a197de9456 initial
2026-01-22 20:23:51 +01:00

361 lines
12 KiB
Python

# Copyright (c) 2019-2024 Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import TYPE_CHECKING, Iterable, Optional, Iterator
from typing_extensions import Self
import logging
from ezdxf.lldxf import validator
from ezdxf.lldxf.attributes import (
DXFAttr,
DXFAttributes,
DefSubclass,
XType,
RETURN_DEFAULT,
group_code_mapping,
)
from ezdxf.lldxf.tags import Tags, DXFTag
from ezdxf.lldxf import const
from ezdxf.math import Vec3, UVec, X_AXIS, Z_AXIS, NULLVEC
from ezdxf.math.transformtools import transform_extrusion
from ezdxf.explode import explode_entity
from ezdxf.audit import AuditError
from .dxfentity import base_class, SubclassProcessor
from .dxfgfx import DXFGraphic, acdb_entity
from .factory import register_entity
from .dimension import OverrideMixin, register_override_handles
from .dimstyleoverride import DimStyleOverride
from .copy import default_copy
if TYPE_CHECKING:
from ezdxf.audit import Auditor
from ezdxf.entities import DXFNamespace, DXFEntity
from ezdxf.layouts import BaseLayout
from ezdxf.lldxf.tagwriter import AbstractTagWriter
from ezdxf.math import Matrix44
from ezdxf.query import EntityQuery
from ezdxf import xref
__all__ = ["Leader"]
logger = logging.getLogger("ezdxf")
acdb_leader = DefSubclass(
"AcDbLeader",
{
"dimstyle": DXFAttr(
3,
default="Standard",
validator=validator.is_valid_table_name,
# no fixer!
),
# Arrowhead flag: 0/1 = no/yes
"has_arrowhead": DXFAttr(
71,
default=1,
optional=True,
validator=validator.is_integer_bool,
fixer=RETURN_DEFAULT,
),
# Leader path type:
# 0 = Straight line segments
# 1 = Spline
"path_type": DXFAttr(
72,
default=0,
optional=True,
validator=validator.is_integer_bool,
fixer=RETURN_DEFAULT,
),
# Annotation type or leader creation flag:
# 0 = Created with text annotation
# 1 = Created with tolerance annotation;
# 2 = Created with block reference annotation
# 3 = Created without any annotation
"annotation_type": DXFAttr(
73,
default=3,
validator=validator.is_in_integer_range(0, 4),
fixer=RETURN_DEFAULT,
),
# Hook line direction flag:
# 1 = Hook line (or end of tangent for a spline leader) is the opposite
# direction from the horizontal vector
# 0 = Hook line (or end of tangent for a spline leader) is the same
# direction as horizontal vector (see code 75)
# DXF reference error: swapped meaning of 1/0
"hookline_direction": DXFAttr(
74,
default=1,
optional=True,
validator=validator.is_integer_bool,
fixer=RETURN_DEFAULT,
),
# Hook line flag: 0/1 = no/yes
"has_hookline": DXFAttr(
75,
default=1,
optional=True,
validator=validator.is_integer_bool,
fixer=RETURN_DEFAULT,
),
# Text annotation height:
"text_height": DXFAttr(
40,
default=1,
optional=True,
validator=validator.is_greater_zero,
fixer=RETURN_DEFAULT,
),
# Text annotation width:
"text_width": DXFAttr(
41,
default=1,
optional=True,
validator=validator.is_greater_zero,
fixer=RETURN_DEFAULT,
),
# 76: Number of vertices in leader (ignored for OPEN)
# 10, 20, 30: Vertex coordinates (one entry for each vertex)
# Color to use if leader's DIMCLRD = BYBLOCK
"block_color": DXFAttr(
77,
default=7,
optional=True,
validator=validator.is_valid_aci_color,
fixer=RETURN_DEFAULT,
),
# Hard reference to associated annotation:
# (mtext, tolerance, or insert entity)
"annotation_handle": DXFAttr(340, default="0", optional=True),
"normal_vector": DXFAttr(
210,
xtype=XType.point3d,
default=Z_AXIS,
optional=True,
validator=validator.is_not_null_vector,
fixer=RETURN_DEFAULT,
),
# 'horizontal' direction for leader
"horizontal_direction": DXFAttr(
211,
xtype=XType.point3d,
default=X_AXIS,
optional=True,
validator=validator.is_not_null_vector,
fixer=RETURN_DEFAULT,
),
# Offset of last leader vertex from block reference insertion point
"leader_offset_block_ref": DXFAttr(
212, xtype=XType.point3d, default=NULLVEC, optional=True
),
# Offset of last leader vertex from annotation placement point
"leader_offset_annotation_placement": DXFAttr(
213, xtype=XType.point3d, default=NULLVEC, optional=True
),
# Xdata belonging to the application ID "ACAD" follows a leader entity if
# any dimension overrides have been applied to this entity. See Dimension
# Style Overrides.
},
)
acdb_leader_group_codes = group_code_mapping(acdb_leader)
@register_entity
class Leader(DXFGraphic, OverrideMixin):
"""DXF LEADER entity"""
DXFTYPE = "LEADER"
DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_leader)
MIN_DXF_VERSION_FOR_EXPORT = const.DXF2000
def __init__(self) -> None:
super().__init__()
self.vertices: list[Vec3] = []
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
"""Copy vertices."""
assert isinstance(entity, Leader)
entity.vertices = Vec3.list(self.vertices)
def load_dxf_attribs(
self, processor: Optional[SubclassProcessor] = None
) -> DXFNamespace:
dxf = super().load_dxf_attribs(processor)
if processor:
tags = processor.subclass_by_index(2)
if tags:
tags = Tags(self.load_vertices(tags))
processor.fast_load_dxfattribs(
dxf, acdb_leader_group_codes, tags, recover=True
)
else:
raise const.DXFStructureError(
f"missing 'AcDbLeader' subclass in LEADER(#{dxf.handle})"
)
return dxf
def load_vertices(self, tags: Tags) -> Iterable[DXFTag]:
for tag in tags:
if tag.code == 10:
self.vertices.append(tag.value)
elif tag.code == 76:
# Number of vertices in leader (ignored for OPEN)
pass
else:
yield tag
def preprocess_export(self, tagwriter: AbstractTagWriter) -> bool:
if len(self.vertices) < 2:
logger.debug(f"Invalid {str(self)}: more than 1 vertex required.")
return False
else:
return True
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
"""Export entity specific data as DXF tags."""
super().export_entity(tagwriter)
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_leader.name)
self.dxf.export_dxf_attribs(
tagwriter,
[
"dimstyle",
"has_arrowhead",
"path_type",
"annotation_type",
"hookline_direction",
"has_hookline",
"text_height",
"text_width",
],
)
self.export_vertices(tagwriter)
self.dxf.export_dxf_attribs(
tagwriter,
[
"block_color",
"annotation_handle",
"normal_vector",
"horizontal_direction",
"leader_offset_block_ref",
"leader_offset_annotation_placement",
],
)
def export_vertices(self, tagwriter: AbstractTagWriter) -> None:
tagwriter.write_tag2(76, len(self.vertices))
for vertex in self.vertices:
tagwriter.write_vertex(10, vertex)
def register_resources(self, registry: xref.Registry) -> None:
"""Register required resources to the resource registry."""
assert self.doc is not None
super().register_resources(registry)
registry.add_dim_style(self.dxf.dimstyle)
# The leader entity cannot register the annotation entity!
if not self.has_xdata_list("ACAD", "DSTYLE"):
return
if self.doc.dxfversion > const.DXF12:
# overridden resources are referenced by handle
register_override_handles(self, registry)
else:
# overridden resources are referenced by name
self.override().register_resources_r12(registry)
def map_resources(self, clone: Self, mapping: xref.ResourceMapper) -> None:
"""Translate resources from self to the copied entity."""
super().map_resources(clone, mapping)
if self.dxf.hasattr("annotation_handle"):
clone.dxf.annotation_handle = mapping.get_handle(self.dxf.annotation_handle)
# DXF R2000+ references overridden resources by group code 1005 handles in the
# XDATA section, which are automatically mapped by the parent class DXFEntity!
assert self.doc is not None
if self.doc.dxfversion > const.DXF12:
return
self_override = self.override()
if not self_override.dimstyle_attribs:
return # has no overrides
assert isinstance(clone, Leader)
self_override.map_resources_r12(clone, mapping)
def override(self) -> DimStyleOverride:
"""Returns the :class:`~ezdxf.entities.DimStyleOverride` object.
.. warning::
The LEADER entity shares only the DIMSTYLE override infrastructure with the
DIMENSION entity but does not support any other features of the DIMENSION
entity!
HANDLE WITH CARE!
"""
return DimStyleOverride(self) # type: ignore
def set_vertices(self, vertices: Iterable[UVec]):
"""Set vertices of the leader, vertices is an iterable of
(x, y [,z]) tuples or :class:`~ezdxf.math.Vec3`.
"""
self.vertices = [Vec3(v) for v in vertices]
def transform(self, m: Matrix44) -> Leader:
"""Transform LEADER entity by transformation matrix `m` inplace."""
self.vertices = list(m.transform_vertices(self.vertices))
self.dxf.normal_vector, _ = transform_extrusion(
self.dxf.normal_vector, m
) # ???
self.dxf.horizontal_direction = m.transform_direction(
self.dxf.horizontal_direction
)
self.post_transform(m)
return self
def __virtual_entities__(self) -> Iterator[DXFGraphic]:
"""Implements the SupportsVirtualEntities protocol."""
from ezdxf.render.leader import virtual_entities
for e in virtual_entities(self):
e.set_source_of_copy(self)
yield e
def virtual_entities(self) -> Iterator[DXFGraphic]:
"""Yields the DXF primitives the LEADER entity is build up as virtual entities.
These entities are located at the original location, but are not stored
in the entity database, have no handle and are not assigned to any
layout.
"""
return self.__virtual_entities__()
def explode(self, target_layout: Optional[BaseLayout] = None) -> EntityQuery:
"""Explode parts of the LEADER entity as DXF primitives into target layout,
if target layout is ``None``, the target layout is the layout of the LEADER
entity. This method destroys the source entity.
Returns an :class:`~ezdxf.query.EntityQuery` container referencing all
DXF primitives.
Args:
target_layout: target layout for the created DXF primitives, ``None`` for
the same layout as the source entity.
"""
return explode_entity(self, target_layout)
def audit(self, auditor: Auditor) -> None:
"""Validity check."""
super().audit(auditor)
if len(self.vertices) < 2:
auditor.fixed_error(
code=AuditError.INVALID_VERTEX_COUNT,
message=f"Deleted entity {str(self)} with invalid vertex count "
f"= {len(self.vertices)}.",
dxf_entity=self,
)
self.destroy()