refactor: excel parse
This commit is contained in:
@@ -0,0 +1,737 @@
|
||||
# Copyright (c) 2019-2024 Manfred Moitzi
|
||||
# License: MIT License
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing_extensions import Self
|
||||
import copy
|
||||
|
||||
from ezdxf.audit import Auditor, AuditError
|
||||
from ezdxf.lldxf import validator
|
||||
from ezdxf.math import NULLVEC, Vec3, Z_AXIS, OCS, Matrix44
|
||||
from ezdxf.lldxf.attributes import (
|
||||
DXFAttr,
|
||||
DXFAttributes,
|
||||
DefSubclass,
|
||||
XType,
|
||||
RETURN_DEFAULT,
|
||||
group_code_mapping,
|
||||
)
|
||||
from ezdxf.lldxf import const
|
||||
from ezdxf.lldxf.types import EMBEDDED_OBJ_MARKER, EMBEDDED_OBJ_STR
|
||||
from ezdxf.enums import MAP_MTEXT_ALIGN_TO_FLAGS, TextHAlign, TextVAlign
|
||||
from ezdxf.tools import set_flag_state
|
||||
from ezdxf.tools.text import (
|
||||
load_mtext_content,
|
||||
fast_plain_mtext,
|
||||
plain_mtext,
|
||||
)
|
||||
|
||||
from .dxfns import SubclassProcessor, DXFNamespace
|
||||
from .dxfentity import base_class
|
||||
from .dxfgfx import acdb_entity, elevation_to_z_axis
|
||||
from .text import Text, acdb_text, acdb_text_group_codes
|
||||
from .mtext import (
|
||||
acdb_mtext_group_codes,
|
||||
MText,
|
||||
export_mtext_content,
|
||||
acdb_mtext,
|
||||
)
|
||||
from .factory import register_entity
|
||||
from .copy import default_copy
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.lldxf.tagwriter import AbstractTagWriter
|
||||
from ezdxf.lldxf.tags import Tags
|
||||
from ezdxf import xref
|
||||
|
||||
|
||||
__all__ = ["AttDef", "Attrib", "copy_attrib_as_text", "BaseAttrib"]
|
||||
|
||||
# Where is it valid to place an ATTRIB entity:
|
||||
# - YES: attached to an INSERT entity
|
||||
# - NO: stand-alone entity in model space - ignored by BricsCAD and TrueView
|
||||
# - NO: stand-alone entity in paper space - ignored by BricsCAD and TrueView
|
||||
# - NO: stand-alone entity in block layout - ignored by BricsCAD and TrueView
|
||||
#
|
||||
# The RECOVER command of BricsCAD removes the stand-alone ATTRIB entities:
|
||||
# "Invalid subentity type AcDbAttribute(<handle>)"
|
||||
#
|
||||
# IMPORTANT: placing ATTRIB at an invalid layout does NOT create an invalid DXF file!
|
||||
#
|
||||
# Where is it valid to place an ATTDEF entity:
|
||||
# - NO: attached to an INSERT entity
|
||||
# - YES: stand-alone entity in a BLOCK layout - BricsCAD and TrueView render the
|
||||
# TAG in the block editor and does not render the ATTDEF as block content
|
||||
# for the INSERT entity.
|
||||
# - YES: stand-alone entity in model space - BricsCAD and TrueView render the
|
||||
# TAG not the default text - the model space is also a block content
|
||||
# (XREF, see also INSERT entity)
|
||||
# - YES: stand-alone entity in paper space - same as model space, although a
|
||||
# paper space can not be used as XREF.
|
||||
|
||||
# DXF Reference for ATTRIB is a total mess and incorrect, the AcDbText subclass
|
||||
# for the ATTRIB entity is the same as for the TEXT entity, but the valign field
|
||||
# from the 2nd AcDbText subclass of the TEXT entity is stored in the
|
||||
# AcDbAttribute subclass:
|
||||
attrib_fields = {
|
||||
# "version": DXFAttr(280, default=0, dxfversion=const.DXF2010),
|
||||
# The "version" tag has the same group code as the lock_position tag!!!!!
|
||||
# Version number: 0 = 2010
|
||||
# This tag is not really used (at least by BricsCAD) but there exists DXF files
|
||||
# which do use this tag: "dxftest\attrib\attrib_with_mtext_R2018.dxf"
|
||||
# ezdxf stores the last group code 280 as "lock_position" attribute and does
|
||||
# not export a version tag for any DXF version.
|
||||
# Tag string (cannot contain spaces):
|
||||
# Mandatory by AutoCAD!
|
||||
"tag": DXFAttr(
|
||||
2,
|
||||
default="",
|
||||
validator=validator.is_valid_attrib_tag,
|
||||
fixer=validator.fix_attrib_tag,
|
||||
),
|
||||
# 1 = Attribute is invisible (does not appear)
|
||||
# 2 = This is a constant attribute
|
||||
# 4 = Verification is required on input of this attribute
|
||||
# 8 = Attribute is preset (no prompt during insertion)
|
||||
"flags": DXFAttr(70, default=0),
|
||||
# Field length (optional) (not currently used)
|
||||
"field_length": DXFAttr(73, default=0, optional=True),
|
||||
# Vertical text justification type (optional); see group code 73 in TEXT
|
||||
"valign": DXFAttr(
|
||||
74,
|
||||
default=0,
|
||||
optional=True,
|
||||
validator=validator.is_in_integer_range(0, 4),
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
# Lock position flag. Locks the position of the attribute within the block
|
||||
# reference, example of double use of group codes in one sub class
|
||||
"lock_position": DXFAttr(
|
||||
280,
|
||||
default=0,
|
||||
dxfversion=const.DXF2007, # tested with BricsCAD 2023/TrueView 2023
|
||||
optional=True,
|
||||
validator=validator.is_integer_bool,
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
# Attribute type:
|
||||
# 1 = single line
|
||||
# 2 = multiline ATTRIB
|
||||
# 4 = multiline ATTDEF
|
||||
"attribute_type": DXFAttr(
|
||||
71,
|
||||
default=const.ATTRIB_TYPE_SINGLE_LINE,
|
||||
dxfversion=const.DXF2018,
|
||||
optional=True,
|
||||
validator=validator.is_one_of({1, 2, 4}),
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
}
|
||||
|
||||
# ATTDEF has an additional field: 'prompt'
|
||||
# DXF attribute definitions are immutable, a shallow copy is sufficient:
|
||||
attdef_fields = dict(attrib_fields)
|
||||
attdef_fields["prompt"] = DXFAttr(
|
||||
3,
|
||||
default="",
|
||||
validator=validator.is_valid_one_line_text,
|
||||
fixer=validator.fix_one_line_text,
|
||||
)
|
||||
|
||||
acdb_attdef = DefSubclass("AcDbAttributeDefinition", attdef_fields)
|
||||
acdb_attdef_group_codes = group_code_mapping(acdb_attdef)
|
||||
acdb_attrib = DefSubclass("AcDbAttribute", attrib_fields)
|
||||
acdb_attrib_group_codes = group_code_mapping(acdb_attrib)
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
# Does subclass AcDbXrecord really exist? Only the documentation in the DXF reference
|
||||
# exists, no real world examples seen so far - it wouldn't be the first error or misleading
|
||||
# information in the DXF reference.
|
||||
# --------------------------------------------------------------------------------------
|
||||
# For XRECORD the tag order is important and group codes appear multiple times,
|
||||
# therefore this attribute definition needs a special treatment!
|
||||
acdb_attdef_xrecord = DefSubclass(
|
||||
"AcDbXrecord",
|
||||
[ # type: ignore
|
||||
# Duplicate record cloning flag (determines how to merge duplicate entries):
|
||||
# 1 = Keep existing
|
||||
("cloning", DXFAttr(280, default=1)),
|
||||
# MText flag:
|
||||
# 2 = multiline attribute
|
||||
# 4 = constant multiline attribute definition
|
||||
("mtext_flag", DXFAttr(70, default=0)),
|
||||
# isReallyLocked flag:
|
||||
# 0 = unlocked
|
||||
# 1 = locked
|
||||
(
|
||||
"really_locked",
|
||||
DXFAttr(
|
||||
70,
|
||||
default=0,
|
||||
validator=validator.is_integer_bool,
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
),
|
||||
# Number of secondary attributes or attribute definitions:
|
||||
("secondary_attribs_count", DXFAttr(70, default=0)),
|
||||
# Hard-pointer id of secondary attribute(s) or attribute definition(s):
|
||||
("secondary_attribs_handle", DXFAttr(340, default="0")),
|
||||
# Alignment point of attribute or attribute definition:
|
||||
("align_point", DXFAttr(10, xtype=XType.point3d, default=NULLVEC)),
|
||||
("current_annotation_scale", DXFAttr(40, default=0)),
|
||||
# attribute or attribute definition tag string
|
||||
(
|
||||
"tag",
|
||||
DXFAttr(
|
||||
2,
|
||||
default="",
|
||||
validator=validator.is_valid_attrib_tag,
|
||||
fixer=validator.fix_attrib_tag,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Just for documentation:
|
||||
# The "attached" MTEXT feature most likely does not exist!
|
||||
#
|
||||
# A special MTEXT entity can follow the ATTDEF and ATTRIB entity, which starts
|
||||
# as a usual DXF entity with (0, 'MTEXT'), so processing can't be done here,
|
||||
# because for ezdxf is this a separated Entity.
|
||||
#
|
||||
# The attached MTEXT entity: owner is None and handle is None
|
||||
# Linked as attribute `attached_mtext`.
|
||||
# I don't have seen this combination of entities in real world examples and is
|
||||
# ignored by ezdxf for now.
|
||||
#
|
||||
# No DXF files available which uses this feature - misleading DXF Reference!?
|
||||
|
||||
# Attrib and Attdef can have embedded MTEXT entities located in the
|
||||
# <Embedded Object> subclass, see issue #258
|
||||
|
||||
|
||||
class BaseAttrib(Text):
|
||||
XRECORD_DEF = acdb_attdef_xrecord
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
# Does subclass AcDbXrecord really exist?
|
||||
self._xrecord: Optional[Tags] = None
|
||||
self._embedded_mtext: Optional[EmbeddedMText] = None
|
||||
|
||||
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
|
||||
"""Copy entity data, xrecord data and embedded MTEXT are not stored
|
||||
in the entity database.
|
||||
"""
|
||||
assert isinstance(entity, BaseAttrib)
|
||||
entity._xrecord = copy.deepcopy(self._xrecord)
|
||||
entity._embedded_mtext = copy.deepcopy(self._embedded_mtext)
|
||||
|
||||
def load_embedded_mtext(self, processor: SubclassProcessor) -> None:
|
||||
if not processor.embedded_objects:
|
||||
return
|
||||
embedded_object = processor.embedded_objects[0]
|
||||
if embedded_object:
|
||||
mtext = EmbeddedMText()
|
||||
mtext.load_dxf_tags(processor)
|
||||
self._embedded_mtext = mtext
|
||||
|
||||
def export_dxf_r2018_features(self, tagwriter: AbstractTagWriter) -> None:
|
||||
tagwriter.write_tag2(71, self.dxf.attribute_type)
|
||||
tagwriter.write_tag2(72, 0) # unknown tag
|
||||
if self.dxf.hasattr("align_point"):
|
||||
# duplicate align point - why?
|
||||
tagwriter.write_vertex(11, self.dxf.align_point)
|
||||
|
||||
if self._xrecord:
|
||||
tagwriter.write_tags(self._xrecord)
|
||||
if self._embedded_mtext:
|
||||
self._embedded_mtext.export_dxf_tags(tagwriter)
|
||||
|
||||
@property
|
||||
def is_const(self) -> bool:
|
||||
"""This is a constant attribute if ``True``."""
|
||||
return bool(self.dxf.flags & const.ATTRIB_CONST)
|
||||
|
||||
@is_const.setter
|
||||
def is_const(self, state: bool) -> None:
|
||||
self.dxf.flags = set_flag_state(self.dxf.flags, const.ATTRIB_CONST, state)
|
||||
|
||||
@property
|
||||
def is_invisible(self) -> bool:
|
||||
"""Attribute is invisible if ``True``."""
|
||||
return bool(self.dxf.flags & const.ATTRIB_INVISIBLE)
|
||||
|
||||
@is_invisible.setter
|
||||
def is_invisible(self, state: bool) -> None:
|
||||
self.dxf.flags = set_flag_state(self.dxf.flags, const.ATTRIB_INVISIBLE, state)
|
||||
|
||||
@property
|
||||
def is_verify(self) -> bool:
|
||||
"""Verification is required on input of this attribute. (interactive CAD
|
||||
application feature)
|
||||
"""
|
||||
return bool(self.dxf.flags & const.ATTRIB_VERIFY)
|
||||
|
||||
@is_verify.setter
|
||||
def is_verify(self, state: bool) -> None:
|
||||
self.dxf.flags = set_flag_state(self.dxf.flags, const.ATTRIB_VERIFY, state)
|
||||
|
||||
@property
|
||||
def is_preset(self) -> bool:
|
||||
"""No prompt during insertion. (interactive CAD application feature)"""
|
||||
return bool(self.dxf.flags & const.ATTRIB_IS_PRESET)
|
||||
|
||||
@is_preset.setter
|
||||
def is_preset(self, state: bool) -> None:
|
||||
self.dxf.flags = set_flag_state(self.dxf.flags, const.ATTRIB_IS_PRESET, state)
|
||||
|
||||
@property
|
||||
def has_embedded_mtext_entity(self) -> bool:
|
||||
"""Returns ``True`` if the entity has an embedded MTEXT entity for multi-line
|
||||
support.
|
||||
"""
|
||||
return bool(self._embedded_mtext)
|
||||
|
||||
def virtual_mtext_entity(self) -> MText:
|
||||
"""Returns the embedded MTEXT entity as a regular but virtual
|
||||
:class:`MText` entity with the same graphical properties as the
|
||||
host entity.
|
||||
"""
|
||||
if not self._embedded_mtext:
|
||||
raise TypeError("no embedded MTEXT entity exist")
|
||||
mtext = self._embedded_mtext.virtual_mtext_entity()
|
||||
mtext.update_dxf_attribs(self.graphic_properties())
|
||||
return mtext
|
||||
|
||||
def plain_mtext(self, fast=True) -> str:
|
||||
"""Returns the embedded MTEXT content without formatting codes.
|
||||
Returns an empty string if no embedded MTEXT entity exist.
|
||||
|
||||
The `fast` mode is accurate if the DXF content was created by
|
||||
reliable (and newer) CAD applications like AutoCAD or BricsCAD.
|
||||
The `accurate` mode is for some rare cases where the content was
|
||||
created by older CAD applications or unreliable DXF libraries and CAD
|
||||
applications.
|
||||
|
||||
The `accurate` mode is **much** slower than the `fast` mode.
|
||||
|
||||
Args:
|
||||
fast: uses the `fast` mode to extract the plain MTEXT content if
|
||||
``True`` or the `accurate` mode if set to ``False``
|
||||
|
||||
"""
|
||||
if self._embedded_mtext:
|
||||
text = self._embedded_mtext.text
|
||||
if fast:
|
||||
return fast_plain_mtext(text, split=False) # type: ignore
|
||||
else:
|
||||
return plain_mtext(text, split=False) # type: ignore
|
||||
return ""
|
||||
|
||||
def set_mtext(self, mtext: MText, graphic_properties=True) -> None:
|
||||
"""Set multi-line properties from a :class:`MText` entity.
|
||||
|
||||
The multi-line ATTRIB/ATTDEF entity requires DXF R2018, otherwise an
|
||||
ordinary single line ATTRIB/ATTDEF entity will be exported.
|
||||
|
||||
Args:
|
||||
mtext: source :class:`MText` entity
|
||||
graphic_properties: copy graphic properties (color, layer, ...) from
|
||||
source MTEXT if ``True``
|
||||
|
||||
"""
|
||||
if self._embedded_mtext is None:
|
||||
self._embedded_mtext = EmbeddedMText()
|
||||
self._embedded_mtext.set_mtext(mtext)
|
||||
_update_content_from_mtext(self, mtext)
|
||||
_update_location_from_mtext(self, mtext)
|
||||
|
||||
# set attribute type:
|
||||
if isinstance(self, Attrib):
|
||||
attribute_type = const.ATTRIB_TYPE_MULTI_LINE
|
||||
else:
|
||||
attribute_type = const.ATTDEF_TYPE_MULTI_LINE
|
||||
self.dxf.attribute_type = attribute_type
|
||||
|
||||
# misc properties
|
||||
self.dxf.style = mtext.dxf.style
|
||||
self.dxf.height = mtext.dxf.char_height
|
||||
self.dxf.discard("width") # controlled in MTEXT by inline codes!
|
||||
self.dxf.discard("oblique") # controlled in MTEXT by inline codes!
|
||||
self.dxf.discard("text_generation_flag")
|
||||
if graphic_properties:
|
||||
self.update_dxf_attribs(mtext.graphic_properties())
|
||||
|
||||
def embed_mtext(self, mtext: MText, graphic_properties=True) -> None:
|
||||
"""Set multi-line properties from a :class:`MText` entity and destroy the
|
||||
source entity afterward.
|
||||
|
||||
The multi-line ATTRIB/ATTDEF entity requires DXF R2018, otherwise an
|
||||
ordinary single line ATTRIB/ATTDEF entity will be exported.
|
||||
|
||||
Args:
|
||||
mtext: source :class:`MText` entity
|
||||
graphic_properties: copy graphic properties (color, layer, ...) from
|
||||
source MTEXT if ``True``
|
||||
|
||||
"""
|
||||
self.set_mtext(mtext, graphic_properties)
|
||||
mtext.destroy()
|
||||
|
||||
def discard_mtext(self) -> None:
|
||||
"""Discard multi-line feature.
|
||||
|
||||
The embedded MTEXT will be removed and the ATTRIB/ATTDEF will be converted to a
|
||||
single-line attribute.
|
||||
"""
|
||||
self._embedded_mtext = None
|
||||
self.dxf.attribute_type = const.ATTRIB_TYPE_SINGLE_LINE
|
||||
|
||||
def register_resources(self, registry: xref.Registry) -> None:
|
||||
"""Register required resources to the resource registry."""
|
||||
super().register_resources(registry)
|
||||
if self._embedded_mtext:
|
||||
self._embedded_mtext.register_resources(registry)
|
||||
|
||||
def map_resources(self, clone: Self, mapping: xref.ResourceMapper) -> None:
|
||||
"""Translate resources from self to the copied entity."""
|
||||
assert isinstance(clone, BaseAttrib)
|
||||
super().map_resources(clone, mapping)
|
||||
if self._embedded_mtext and clone._embedded_mtext:
|
||||
self._embedded_mtext.map_resources(clone._embedded_mtext, mapping)
|
||||
# todo: map handles in embedded XRECORD if a real world example shows up
|
||||
|
||||
def transform(self, m: Matrix44) -> Self:
|
||||
if self._embedded_mtext is None:
|
||||
super().transform(m)
|
||||
else:
|
||||
mtext = self._embedded_mtext.virtual_mtext_entity()
|
||||
mtext.transform(m)
|
||||
self.set_mtext(mtext, graphic_properties=False)
|
||||
self.post_transform(m)
|
||||
return self
|
||||
|
||||
def audit(self, auditor: Auditor) -> None:
|
||||
"""Validity check."""
|
||||
super().audit(auditor)
|
||||
if not self.dxf.hasattr("tag"):
|
||||
auditor.fixed_error(
|
||||
code=AuditError.TAG_ATTRIBUTE_MISSING,
|
||||
message=f'Missing mandatory "tag" attribute, entity {str(self)} deleted.',
|
||||
)
|
||||
auditor.trash(self)
|
||||
|
||||
|
||||
def _update_content_from_mtext(text: Text, mtext: MText) -> None:
|
||||
content = mtext.plain_text(split=True, fast=True)
|
||||
if content:
|
||||
# In contrast to AutoCAD, just set the first line as single line
|
||||
# ATTRIB content. AutoCAD concatenates all lines into a single
|
||||
# "Line1\PLine2\P...", which (imho) is not very useful.
|
||||
text.dxf.text = content[0]
|
||||
|
||||
|
||||
def _update_location_from_mtext(text: Text, mtext: MText) -> None:
|
||||
# TEXT is an OCS entity, MTEXT is a WCS entity
|
||||
dxf = text.dxf
|
||||
insert = Vec3(mtext.dxf.insert)
|
||||
extrusion = Vec3(mtext.dxf.extrusion)
|
||||
text_direction = mtext.get_text_direction()
|
||||
if extrusion.isclose(Z_AXIS): # most common case
|
||||
dxf.rotation = text_direction.angle_deg
|
||||
else:
|
||||
ocs = OCS(extrusion)
|
||||
insert = ocs.from_wcs(insert)
|
||||
dxf.extrusion = extrusion.normalize()
|
||||
dxf.rotation = ocs.from_wcs(text_direction).angle_deg
|
||||
|
||||
dxf.insert = insert
|
||||
dxf.align_point = insert # the same point for all MTEXT alignments!
|
||||
dxf.halign, dxf.valign = MAP_MTEXT_ALIGN_TO_FLAGS.get(
|
||||
mtext.dxf.attachment_point, (TextHAlign.LEFT, TextVAlign.TOP)
|
||||
)
|
||||
|
||||
|
||||
@register_entity
|
||||
class AttDef(BaseAttrib):
|
||||
"""DXF ATTDEF entity"""
|
||||
|
||||
DXFTYPE = "ATTDEF"
|
||||
# Don't add acdb_attdef_xrecord here:
|
||||
DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_text, acdb_attdef)
|
||||
|
||||
def load_dxf_attribs(
|
||||
self, processor: Optional[SubclassProcessor] = None
|
||||
) -> DXFNamespace:
|
||||
dxf = super(Text, self).load_dxf_attribs(processor)
|
||||
# Do not call Text loader.
|
||||
if processor:
|
||||
processor.fast_load_dxfattribs(dxf, acdb_text_group_codes, 2, recover=True)
|
||||
processor.fast_load_dxfattribs(
|
||||
dxf, acdb_attdef_group_codes, 3, recover=True
|
||||
)
|
||||
self._xrecord = processor.find_subclass(self.XRECORD_DEF.name) # type: ignore
|
||||
self.load_embedded_mtext(processor)
|
||||
if processor.r12:
|
||||
# Transform elevation attribute from R11 to z-axis values:
|
||||
elevation_to_z_axis(dxf, ("insert", "align_point"))
|
||||
|
||||
return dxf
|
||||
|
||||
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
||||
# Text() writes 2x AcDbText which is not suitable for AttDef()
|
||||
self.export_acdb_entity(tagwriter)
|
||||
self.export_acdb_text(tagwriter)
|
||||
self.export_acdb_attdef(tagwriter)
|
||||
if tagwriter.dxfversion >= const.DXF2018:
|
||||
self.export_dxf_r2018_features(tagwriter)
|
||||
|
||||
def export_acdb_attdef(self, tagwriter: AbstractTagWriter) -> None:
|
||||
if tagwriter.dxfversion > const.DXF12:
|
||||
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_attdef.name)
|
||||
self.dxf.export_dxf_attribs(
|
||||
tagwriter,
|
||||
[
|
||||
# write version tag (280, 0) here, if required in the future
|
||||
"prompt",
|
||||
"tag",
|
||||
"flags",
|
||||
"field_length",
|
||||
"valign",
|
||||
"lock_position",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@register_entity
|
||||
class Attrib(BaseAttrib):
|
||||
"""DXF ATTRIB entity"""
|
||||
|
||||
DXFTYPE = "ATTRIB"
|
||||
# Don't add acdb_attdef_xrecord here:
|
||||
DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_text, acdb_attrib)
|
||||
|
||||
def load_dxf_attribs(
|
||||
self, processor: Optional[SubclassProcessor] = None
|
||||
) -> DXFNamespace:
|
||||
dxf = super(Text, self).load_dxf_attribs(processor)
|
||||
# Do not call Text loader.
|
||||
if processor:
|
||||
processor.fast_load_dxfattribs(dxf, acdb_text_group_codes, 2, recover=True)
|
||||
processor.fast_load_dxfattribs(
|
||||
dxf, acdb_attrib_group_codes, 3, recover=True
|
||||
)
|
||||
self._xrecord = processor.find_subclass(self.XRECORD_DEF.name) # type: ignore
|
||||
self.load_embedded_mtext(processor)
|
||||
if processor.r12:
|
||||
# Transform elevation attribute from R11 to z-axis values:
|
||||
elevation_to_z_axis(dxf, ("insert", "align_point"))
|
||||
return dxf
|
||||
|
||||
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
||||
# Text() writes 2x AcDbText which is not suitable for AttDef()
|
||||
self.export_acdb_entity(tagwriter)
|
||||
self.export_acdb_attrib_text(tagwriter)
|
||||
self.export_acdb_attrib(tagwriter)
|
||||
if tagwriter.dxfversion >= const.DXF2018:
|
||||
self.export_dxf_r2018_features(tagwriter)
|
||||
|
||||
def export_acdb_attrib_text(self, tagwriter: AbstractTagWriter) -> None:
|
||||
# Despite the similarities to TEXT, it is different to
|
||||
# Text.export_acdb_text():
|
||||
if tagwriter.dxfversion > const.DXF12:
|
||||
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_text.name)
|
||||
self.dxf.export_dxf_attribs(
|
||||
tagwriter,
|
||||
[
|
||||
"insert",
|
||||
"height",
|
||||
"text",
|
||||
"thickness",
|
||||
"rotation",
|
||||
"oblique",
|
||||
"style",
|
||||
"width",
|
||||
"halign",
|
||||
"align_point",
|
||||
"text_generation_flag",
|
||||
"extrusion",
|
||||
],
|
||||
)
|
||||
|
||||
def export_acdb_attrib(self, tagwriter: AbstractTagWriter) -> None:
|
||||
if tagwriter.dxfversion > const.DXF12:
|
||||
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_attrib.name)
|
||||
self.dxf.export_dxf_attribs(
|
||||
tagwriter,
|
||||
[
|
||||
# write version tag (280, 0) here, if required in the future
|
||||
"tag",
|
||||
"flags",
|
||||
"field_length",
|
||||
"valign",
|
||||
"lock_position",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
IGNORE_FROM_ATTRIB = {
|
||||
"handle",
|
||||
"owner",
|
||||
"version",
|
||||
"prompt",
|
||||
"tag",
|
||||
"flags",
|
||||
"field_length",
|
||||
"lock_position",
|
||||
}
|
||||
|
||||
|
||||
def copy_attrib_as_text(attrib: BaseAttrib):
|
||||
"""Returns the content of the ATTRIB/ATTDEF entity as a new virtual TEXT or
|
||||
MTEXT entity.
|
||||
|
||||
"""
|
||||
if attrib.has_embedded_mtext_entity:
|
||||
return attrib.virtual_mtext_entity()
|
||||
dxfattribs = attrib.dxfattribs(drop=IGNORE_FROM_ATTRIB)
|
||||
return Text.new(dxfattribs=dxfattribs, doc=attrib.doc)
|
||||
|
||||
|
||||
class EmbeddedMTextNS(DXFNamespace):
|
||||
_DXFATTRIBS = DXFAttributes(acdb_mtext)
|
||||
|
||||
@property
|
||||
def dxfattribs(self) -> DXFAttributes:
|
||||
return self._DXFATTRIBS
|
||||
|
||||
@property
|
||||
def dxftype(self) -> str:
|
||||
return "Embedded MText"
|
||||
|
||||
|
||||
class EmbeddedMText:
|
||||
"""Representation of the embedded MTEXT object in ATTRIB and ATTDEF.
|
||||
|
||||
Introduced in DXF R2018? The DXF reference of the `MTEXT`_ entity
|
||||
documents only the attached MTEXT entity. The ODA DWG specs includes all
|
||||
MTEXT attributes of MTEXT starting at group code 10
|
||||
|
||||
Stores the required parameters to be shown as as MTEXT.
|
||||
The AcDbText subclass contains the first line of the embedded MTEXT as
|
||||
plain text content as group code 1, but this tag seems not to be maintained
|
||||
if the ATTRIB entity is copied.
|
||||
|
||||
Some DXF attributes are duplicated and maintained by the CAD application:
|
||||
|
||||
- textstyle: same group code 7 (AcDbText, EmbeddedObject)
|
||||
- text (char) height: same group code 40 (AcDbText, EmbeddedObject)
|
||||
|
||||
.. _MTEXT: https://help.autodesk.com/view/OARX/2018/ENU/?guid=GUID-7DD8B495-C3F8-48CD-A766-14F9D7D0DD9B
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# Attribute "dxf" contains the DXF attributes defined in subclass
|
||||
# "AcDbMText"
|
||||
self.dxf = EmbeddedMTextNS()
|
||||
self.text: str = ""
|
||||
|
||||
def copy(self) -> EmbeddedMText:
|
||||
copy_ = EmbeddedMText()
|
||||
copy_.dxf = copy.deepcopy(self.dxf)
|
||||
return copy_
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def load_dxf_tags(self, processor: SubclassProcessor) -> None:
|
||||
tags = processor.fast_load_dxfattribs(
|
||||
self.dxf,
|
||||
group_code_mapping=acdb_mtext_group_codes,
|
||||
subclass=processor.embedded_objects[0],
|
||||
recover=False,
|
||||
)
|
||||
self.text = load_mtext_content(tags)
|
||||
|
||||
def virtual_mtext_entity(self) -> MText:
|
||||
"""Returns the embedded MTEXT entity as regular but virtual MTEXT
|
||||
entity. This entity does not have the graphical attributes of the host
|
||||
entity (ATTRIB/ATTDEF).
|
||||
|
||||
"""
|
||||
mtext = MText.new(dxfattribs=self.dxf.all_existing_dxf_attribs())
|
||||
mtext.text = self.text
|
||||
return mtext
|
||||
|
||||
def set_mtext(self, mtext: MText) -> None:
|
||||
"""Set embedded MTEXT attributes from given `mtext` entity."""
|
||||
self.text = mtext.text
|
||||
dxf = self.dxf
|
||||
for k, v in mtext.dxf.all_existing_dxf_attribs().items():
|
||||
if dxf.is_supported(k):
|
||||
dxf.set(k, v)
|
||||
|
||||
def set_required_dxf_attributes(self):
|
||||
# These attributes are always present in DXF files created by Autocad:
|
||||
dxf = self.dxf
|
||||
for key, default in (
|
||||
("insert", NULLVEC),
|
||||
("char_height", 2.5),
|
||||
("width", 0.0),
|
||||
("defined_height", 0.0),
|
||||
("attachment_point", 1),
|
||||
("flow_direction", 5),
|
||||
("style", "Standard"),
|
||||
("line_spacing_style", 1),
|
||||
("line_spacing_factor", 1.0),
|
||||
):
|
||||
if not dxf.hasattr(key):
|
||||
dxf.set(key, default)
|
||||
|
||||
def export_dxf_tags(self, tagwriter: AbstractTagWriter) -> None:
|
||||
"""Export embedded MTEXT as "Embedded Object"."""
|
||||
tagwriter.write_tag2(EMBEDDED_OBJ_MARKER, EMBEDDED_OBJ_STR)
|
||||
self.set_required_dxf_attributes()
|
||||
self.dxf.export_dxf_attribs(
|
||||
tagwriter,
|
||||
[
|
||||
"insert",
|
||||
"char_height",
|
||||
"width",
|
||||
"defined_height",
|
||||
"attachment_point",
|
||||
"flow_direction",
|
||||
],
|
||||
)
|
||||
export_mtext_content(self.text, tagwriter)
|
||||
self.dxf.export_dxf_attribs(
|
||||
tagwriter,
|
||||
[
|
||||
"style",
|
||||
"extrusion",
|
||||
"text_direction",
|
||||
"rect_width",
|
||||
"rect_height",
|
||||
"rotation",
|
||||
"line_spacing_style",
|
||||
"line_spacing_factor",
|
||||
"box_fill_scale",
|
||||
"bg_fill",
|
||||
"bg_fill_color",
|
||||
"bg_fill_true_color",
|
||||
"bg_fill_color_name",
|
||||
"bg_fill_transparency",
|
||||
],
|
||||
)
|
||||
|
||||
def register_resources(self, registry: xref.Registry) -> None:
|
||||
"""Register required resources to the resource registry."""
|
||||
if self.dxf.hasattr("style"):
|
||||
registry.add_text_style(self.dxf.style)
|
||||
|
||||
def map_resources(self, clone: EmbeddedMText, mapping: xref.ResourceMapper) -> None:
|
||||
"""Translate resources from self to the copied entity."""
|
||||
if clone.dxf.hasattr("style"):
|
||||
clone.dxf.style = mapping.get_text_style(clone.dxf.style)
|
||||
Reference in New Issue
Block a user