refactor: excel parse
This commit is contained in:
@@ -0,0 +1,687 @@
|
||||
# Copyright (c) 2020-2025, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from __future__ import annotations
|
||||
from typing import Any, Optional, Union, Iterable, TYPE_CHECKING, Set
|
||||
import logging
|
||||
import itertools
|
||||
from ezdxf import options
|
||||
from ezdxf.lldxf import const
|
||||
from ezdxf.lldxf.attributes import XType, DXFAttributes, DXFAttr
|
||||
from ezdxf.lldxf.types import cast_value, dxftag
|
||||
from ezdxf.lldxf.tags import Tags
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.lldxf.extendedtags import ExtendedTags
|
||||
from ezdxf.entities import DXFEntity
|
||||
from ezdxf.lldxf.tagwriter import AbstractTagWriter
|
||||
|
||||
|
||||
__all__ = ["DXFNamespace", "SubclassProcessor"]
|
||||
logger = logging.getLogger("ezdxf")
|
||||
|
||||
ERR_INVALID_DXF_ATTRIB = 'Invalid DXF attribute "{}" for entity {}'
|
||||
ERR_DXF_ATTRIB_NOT_EXITS = 'DXF attribute "{}" does not exist'
|
||||
|
||||
# supported event handler called by setting DXF attributes
|
||||
# for usage, implement a method named like the dict-value, that accepts the new
|
||||
# value as argument e.g.:
|
||||
# Polyline.on_layer_change(name) -> changes also layers of all vertices
|
||||
|
||||
SETTER_EVENTS = {
|
||||
"layer": "on_layer_change",
|
||||
"linetype": "on_linetype_change",
|
||||
"style": "on_style_change",
|
||||
"dimstyle": "on_dimstyle_change",
|
||||
}
|
||||
EXCLUDE_FROM_UPDATE = frozenset(["_entity", "handle", "owner"])
|
||||
|
||||
|
||||
class DXFNamespace:
|
||||
""":class:`DXFNamespace` manages all named DXF attributes of an entity.
|
||||
|
||||
The DXFNamespace.__dict__ is used as DXF attribute storage, therefore only
|
||||
valid Python names can be used as attrib name.
|
||||
|
||||
The namespace can only contain immutable objects: string, int, float, bool,
|
||||
Vec3. Because of the immutability, copy and deepcopy are the same.
|
||||
|
||||
(internal class)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
processor: Optional[SubclassProcessor] = None,
|
||||
entity: Optional[DXFEntity] = None,
|
||||
):
|
||||
if processor:
|
||||
base_class = processor.base_class
|
||||
handle_code = 105 if base_class[0].value == "DIMSTYLE" else 5
|
||||
# CLASS entities have no handle.
|
||||
# TABLE entities have no handle if loaded from a DXF R12 file.
|
||||
# Owner tag is None if loaded from a DXF R12 file
|
||||
handle = None
|
||||
owner = None
|
||||
for tag in base_class:
|
||||
group_code = tag.code
|
||||
if group_code == handle_code:
|
||||
handle = tag.value
|
||||
if owner:
|
||||
break
|
||||
elif group_code == 330:
|
||||
owner = tag.value
|
||||
if handle:
|
||||
break
|
||||
self.rewire(entity, handle, owner)
|
||||
else:
|
||||
self.reset_handles()
|
||||
self.rewire(entity)
|
||||
|
||||
def copy(self, entity: DXFEntity):
|
||||
namespace = self.__class__()
|
||||
for k, v in self.__dict__.items():
|
||||
namespace.__dict__[k] = v
|
||||
namespace.rewire(entity)
|
||||
return namespace
|
||||
|
||||
def __deepcopy__(self, memodict: Optional[dict] = None):
|
||||
return self.copy(self._entity)
|
||||
|
||||
def __getstate__(self) -> object:
|
||||
return self.__dict__
|
||||
|
||||
def __setstate__(self, state: object) -> None:
|
||||
if not isinstance(state, dict):
|
||||
raise TypeError(f"invalid state: {type(state).__name__}")
|
||||
# bypass __setattr__
|
||||
object.__setattr__(self, "__dict__", state)
|
||||
|
||||
def reset_handles(self):
|
||||
"""Reset handle and owner to None."""
|
||||
self.__dict__["handle"] = None
|
||||
self.__dict__["owner"] = None
|
||||
|
||||
def rewire(
|
||||
self,
|
||||
entity: Optional[DXFEntity],
|
||||
handle: Optional[str] = None,
|
||||
owner: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Rewire DXF namespace with parent entity
|
||||
|
||||
Args:
|
||||
entity: new associated entity
|
||||
handle: new handle or None
|
||||
owner: new entity owner handle or None
|
||||
|
||||
"""
|
||||
# bypass __setattr__()
|
||||
self.__dict__["_entity"] = entity
|
||||
if handle is not None:
|
||||
self.__dict__["handle"] = handle
|
||||
if owner is not None:
|
||||
self.__dict__["owner"] = owner
|
||||
|
||||
def __getattr__(self, key: str) -> Any:
|
||||
"""Called if DXF attribute `key` does not exist, returns the DXF
|
||||
default value or ``None``.
|
||||
|
||||
Raises:
|
||||
DXFAttributeError: attribute `key` is not supported
|
||||
|
||||
"""
|
||||
attrib_def: Optional[DXFAttr] = self.dxfattribs.get(key)
|
||||
if attrib_def:
|
||||
if attrib_def.xtype == XType.callback:
|
||||
return attrib_def.get_callback_value(self._entity)
|
||||
else:
|
||||
return attrib_def.default
|
||||
else:
|
||||
raise const.DXFAttributeError(
|
||||
ERR_INVALID_DXF_ATTRIB.format(key, self.dxftype)
|
||||
)
|
||||
|
||||
def __setattr__(self, key: str, value: Any) -> None:
|
||||
"""Set DXF attribute `key` to `value`.
|
||||
|
||||
Raises:
|
||||
DXFAttributeError: attribute `key` is not supported
|
||||
|
||||
"""
|
||||
|
||||
def entity() -> str:
|
||||
# DXFNamespace is maybe not assigned to the entity yet:
|
||||
handle = self.get("handle")
|
||||
_entity = self._entity
|
||||
if _entity:
|
||||
return _entity.dxftype() + f"(#{handle})"
|
||||
else:
|
||||
return f"#{handle}"
|
||||
|
||||
def check(value):
|
||||
value = cast_value(attrib_def.code, value)
|
||||
if not attrib_def.is_valid_value(value):
|
||||
if attrib_def.fixer:
|
||||
value = attrib_def.fixer(value)
|
||||
logger.debug(
|
||||
f'Fixed invalid attribute "{key}" in entity'
|
||||
f' {entity()} to "{str(value)}".'
|
||||
)
|
||||
else:
|
||||
raise const.DXFValueError(
|
||||
f'Invalid value {str(value)} for attribute "{key}" in '
|
||||
f"entity {entity()}."
|
||||
)
|
||||
return value
|
||||
|
||||
attrib_def: Optional[DXFAttr] = self.dxfattribs.get(key)
|
||||
if attrib_def:
|
||||
if attrib_def.xtype == XType.callback:
|
||||
attrib_def.set_callback_value(self._entity, value)
|
||||
else:
|
||||
self.__dict__[key] = check(value)
|
||||
else:
|
||||
raise const.DXFAttributeError(
|
||||
ERR_INVALID_DXF_ATTRIB.format(key, self.dxftype)
|
||||
)
|
||||
|
||||
if key in SETTER_EVENTS:
|
||||
handler = getattr(self._entity, SETTER_EVENTS[key], None)
|
||||
if handler:
|
||||
handler(value)
|
||||
|
||||
def __delattr__(self, key: str) -> None:
|
||||
"""Delete DXF attribute `key`.
|
||||
|
||||
Raises:
|
||||
DXFAttributeError: attribute `key` does not exist
|
||||
|
||||
"""
|
||||
if self.hasattr(key):
|
||||
del self.__dict__[key]
|
||||
else:
|
||||
raise const.DXFAttributeError(ERR_DXF_ATTRIB_NOT_EXITS.format(key))
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
"""Returns value of DXF attribute `key` or the given `default` value
|
||||
not DXF default value for unset attributes.
|
||||
|
||||
Raises:
|
||||
DXFAttributeError: attribute `key` is not supported
|
||||
|
||||
"""
|
||||
# callback values should not exist as attribute in __dict__
|
||||
if self.hasattr(key):
|
||||
# do not return the DXF default value
|
||||
return self.__dict__[key]
|
||||
attrib_def: Optional["DXFAttr"] = self.dxfattribs.get(key)
|
||||
if attrib_def:
|
||||
if attrib_def.xtype == XType.callback:
|
||||
return attrib_def.get_callback_value(self._entity)
|
||||
else:
|
||||
return default # return give default
|
||||
else:
|
||||
raise const.DXFAttributeError(
|
||||
ERR_INVALID_DXF_ATTRIB.format(key, self.dxftype)
|
||||
)
|
||||
|
||||
def get_default(self, key: str) -> Any:
|
||||
"""Returns DXF default value for unset DXF attribute `key`."""
|
||||
value = self.get(key, None)
|
||||
return self.dxf_default_value(key) if value is None else value
|
||||
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
"""Set DXF attribute `key` to `value`.
|
||||
|
||||
Raises:
|
||||
DXFAttributeError: attribute `key` is not supported
|
||||
|
||||
"""
|
||||
self.__setattr__(key, value)
|
||||
|
||||
def unprotected_set(self, key: str, value: Any) -> None:
|
||||
"""Set DXF attribute `key` to `value` without any validity checks.
|
||||
|
||||
Used for fast attribute setting without validity checks at loading time.
|
||||
|
||||
(internal API)
|
||||
"""
|
||||
self.__dict__[key] = value
|
||||
|
||||
def all_existing_dxf_attribs(self) -> dict:
|
||||
"""Returns all existing DXF attributes, except DXFEntity back-link."""
|
||||
attribs = dict(self.__dict__)
|
||||
del attribs["_entity"]
|
||||
return attribs
|
||||
|
||||
def update(
|
||||
self,
|
||||
dxfattribs: dict[str, Any],
|
||||
*,
|
||||
exclude: Optional[Set[str]] = None,
|
||||
ignore_errors=False,
|
||||
) -> None:
|
||||
"""Update DXF namespace attributes from a dict."""
|
||||
if exclude is None:
|
||||
exclude = EXCLUDE_FROM_UPDATE # type: ignore
|
||||
else: # always exclude "_entity" back-link
|
||||
exclude = {"_entity"} | exclude
|
||||
|
||||
set_attribute = self.__setattr__
|
||||
for k, v in dxfattribs.items():
|
||||
if k not in exclude: # type: ignore
|
||||
try:
|
||||
set_attribute(k, v)
|
||||
except (AttributeError, ValueError):
|
||||
if not ignore_errors:
|
||||
raise
|
||||
|
||||
def discard(self, key: str) -> None:
|
||||
"""Delete DXF attribute `key` silently without any exception."""
|
||||
try:
|
||||
del self.__dict__[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def is_supported(self, key: str) -> bool:
|
||||
"""Returns True if DXF attribute `key` is supported else False.
|
||||
Does not grant that attribute `key` really exists and does not
|
||||
check if the actual DXF version of the document supports this
|
||||
attribute, unsupported attributes will be ignored at export.
|
||||
|
||||
"""
|
||||
return key in self.dxfattribs
|
||||
|
||||
def hasattr(self, key: str) -> bool:
|
||||
"""Returns True if attribute `key` really exists else False."""
|
||||
return key in self.__dict__
|
||||
|
||||
@property
|
||||
def dxftype(self) -> str:
|
||||
"""Returns the DXF entity type."""
|
||||
return self._entity.DXFTYPE
|
||||
|
||||
@property
|
||||
def dxfattribs(self) -> DXFAttributes:
|
||||
"""Returns the DXF attribute definition."""
|
||||
return self._entity.DXFATTRIBS
|
||||
|
||||
def dxf_default_value(self, key: str) -> Any:
|
||||
"""Returns the default value as defined in the DXF standard."""
|
||||
attrib: Optional[DXFAttr] = self.dxfattribs.get(key)
|
||||
if attrib:
|
||||
return attrib.default
|
||||
else:
|
||||
return None
|
||||
|
||||
def export_dxf_attribs(
|
||||
self, tagwriter: AbstractTagWriter, attribs: Union[str, Iterable]
|
||||
) -> None:
|
||||
"""Exports DXF attribute `name` by `tagwriter`. Non-optional attributes
|
||||
are forced and optional tags are only written if different to default
|
||||
value. DXF version check is always on: does not export DXF attribs
|
||||
which are not supported by tagwriter.dxfversion.
|
||||
|
||||
Args:
|
||||
tagwriter: tag writer object
|
||||
attribs: DXF attribute name as string or an iterable of names
|
||||
|
||||
"""
|
||||
if isinstance(attribs, str):
|
||||
self._export_dxf_attribute_optional(tagwriter, attribs)
|
||||
else:
|
||||
for name in attribs:
|
||||
self._export_dxf_attribute_optional(tagwriter, name)
|
||||
|
||||
def _export_dxf_attribute_optional(
|
||||
self, tagwriter: AbstractTagWriter, name: str
|
||||
) -> None:
|
||||
"""Exports DXF attribute `name` by `tagwriter`.
|
||||
|
||||
Optional tags are only written if they differ from the default value.
|
||||
|
||||
"""
|
||||
attrib: Optional[DXFAttr] = self.dxfattribs.get(name)
|
||||
if attrib is None:
|
||||
raise const.DXFAttributeError(
|
||||
ERR_INVALID_DXF_ATTRIB.format(name, self.dxftype)
|
||||
)
|
||||
|
||||
optional = attrib.optional
|
||||
default = attrib.default
|
||||
value = self.get(name, None)
|
||||
|
||||
# Force default value e.g. layer
|
||||
if value is None and not optional:
|
||||
# default value can be None!
|
||||
value = default
|
||||
|
||||
if value is None:
|
||||
logger.debug(
|
||||
f"DXF attribute '{name}' not written because its a None value."
|
||||
)
|
||||
return
|
||||
|
||||
# Do not write explicit optional attribs if equal to the default value
|
||||
if (
|
||||
optional
|
||||
and (not tagwriter.force_optional)
|
||||
and default is not None
|
||||
and default == value
|
||||
):
|
||||
return
|
||||
_export_group_codes(tagwriter, attrib, value)
|
||||
|
||||
def export_dxf_attribute_if_exists(
|
||||
self, tagwriter: AbstractTagWriter, name: str
|
||||
) -> None:
|
||||
"""Exports DXF attribute `name` by `tagwriter` if exists.
|
||||
|
||||
If the attribute exists, and it's not None it will be written, the optional-flag
|
||||
is ignored.
|
||||
|
||||
No default value will be written if the attribute doesn't exist!
|
||||
This method can not be used for attributes that are required (e.g. layer)!
|
||||
|
||||
"""
|
||||
if not self.hasattr(name):
|
||||
return
|
||||
|
||||
attrib: Optional[DXFAttr] = self.dxfattribs.get(name)
|
||||
assert (
|
||||
attrib is not None
|
||||
), f"existing DXF attribute '{name}' has no definition class - internal error"
|
||||
|
||||
value = self.get(name)
|
||||
if value is None:
|
||||
logger.debug(
|
||||
f"DXF attribute '{name}' not written because its a None value."
|
||||
)
|
||||
return
|
||||
_export_group_codes(tagwriter, attrib, value)
|
||||
|
||||
|
||||
def _export_group_codes(
|
||||
tagwriter: AbstractTagWriter, attrib: DXFAttr, value: Any
|
||||
) -> None:
|
||||
assert attrib is not None
|
||||
assert value is not None
|
||||
|
||||
# Do write the attribute if the export DXF version is lower than the minimal
|
||||
# required DXF version for the attribute.
|
||||
if tagwriter.dxfversion < attrib.dxfversion:
|
||||
return
|
||||
|
||||
# For explicit 2D points export only x- and y-coordinates.
|
||||
if attrib.xtype == XType.point2d and len(value) > 2:
|
||||
try: # Vec3, Vec2
|
||||
value = (value.x, value.y)
|
||||
except AttributeError:
|
||||
value = value[:2]
|
||||
|
||||
if isinstance(value, str):
|
||||
assert "\n" not in value, "line break '\\n' not allowed"
|
||||
assert "\r" not in value, "line break '\\r' not allowed"
|
||||
tag = dxftag(attrib.code, value)
|
||||
tagwriter.write_tag(tag)
|
||||
|
||||
|
||||
BASE_CLASS_CODES = {0, 5, 102, 330}
|
||||
|
||||
|
||||
class SubclassProcessor:
|
||||
"""Helper class for loading tags into entities. (internal class)"""
|
||||
|
||||
def __init__(self, tags: ExtendedTags, dxfversion: Optional[str] = None):
|
||||
if len(tags.subclasses) == 0:
|
||||
raise ValueError("Invalid tags.")
|
||||
self.subclasses: list[Tags] = list(tags.subclasses) # copy subclasses
|
||||
self.embedded_objects: list[Tags] = tags.embedded_objects or []
|
||||
self.dxfversion: Optional[str] = dxfversion
|
||||
# DXF R12 and prior have no subclass marker system, all tags of an
|
||||
# entity in one flat list.
|
||||
# Later DXF versions have at least 2 subclasses base_class and
|
||||
# AcDbEntity.
|
||||
# Exception: CLASS has also only one subclass and no subclass marker,
|
||||
# handled as DXF R12 entity
|
||||
self.r12: bool = (dxfversion == const.DXF12) or (len(self.subclasses) == 1)
|
||||
self.name: str = tags.dxftype()
|
||||
self.handle: str
|
||||
try:
|
||||
self.handle = tags.get_handle()
|
||||
except const.DXFValueError:
|
||||
self.handle = "<?>"
|
||||
|
||||
@property
|
||||
def base_class(self):
|
||||
return self.subclasses[0]
|
||||
|
||||
def log_unprocessed_tags(
|
||||
self,
|
||||
unprocessed_tags: Iterable,
|
||||
subclass="<?>",
|
||||
handle: Optional[str] = None,
|
||||
) -> None:
|
||||
if options.log_unprocessed_tags:
|
||||
for tag in unprocessed_tags:
|
||||
entity = ""
|
||||
if handle:
|
||||
entity = f" in entity #{handle}"
|
||||
logger.info(f"ignored {repr(tag)} in subclass {subclass}" + entity)
|
||||
|
||||
def find_subclass(self, name: str) -> Optional[Tags]:
|
||||
for subclass in self.subclasses:
|
||||
if len(subclass) and subclass[0].value == name:
|
||||
return subclass
|
||||
return None
|
||||
|
||||
def subclass_by_index(self, index: int) -> Optional[Tags]:
|
||||
try:
|
||||
return self.subclasses[index]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def detect_implementation_version(
|
||||
self, subclass_index: int, group_code: int, default: int
|
||||
) -> int:
|
||||
subclass = self.subclass_by_index(subclass_index)
|
||||
if subclass and len(subclass) > 1:
|
||||
# the version tag has to be the 2nd tag after the subclass marker
|
||||
tag = subclass[1]
|
||||
if tag.code == group_code:
|
||||
return tag.value
|
||||
return default
|
||||
|
||||
# TODO: rename to complex_dxfattribs_loader()
|
||||
def fast_load_dxfattribs(
|
||||
self,
|
||||
dxf: DXFNamespace,
|
||||
group_code_mapping: dict[int, Union[str, list]],
|
||||
subclass: Union[int, str, Tags],
|
||||
*,
|
||||
recover=False,
|
||||
log=True,
|
||||
) -> Tags:
|
||||
"""Load DXF attributes into the DXF namespace and returns the
|
||||
unprocessed tags without leading subclass marker(100, AcDb...).
|
||||
Bypasses the DXF attribute validity checks.
|
||||
|
||||
Args:
|
||||
dxf: entity DXF namespace
|
||||
group_code_mapping: group code to DXF attribute name mapping,
|
||||
callback attributes have to be marked with a leading "*"
|
||||
subclass: subclass by index, by name or as Tags()
|
||||
recover: recover graphic attributes
|
||||
log: enable/disable logging of unprocessed tags
|
||||
|
||||
"""
|
||||
if self.r12:
|
||||
tags = self.subclasses[0]
|
||||
else:
|
||||
if isinstance(subclass, int):
|
||||
tags = self.subclass_by_index(subclass) # type: ignore
|
||||
elif isinstance(subclass, str):
|
||||
tags = self.find_subclass(subclass) # type: ignore
|
||||
else:
|
||||
tags = subclass
|
||||
|
||||
unprocessed_tags = Tags()
|
||||
if tags is None or len(tags) == 0:
|
||||
return unprocessed_tags
|
||||
|
||||
processed_names: set[str] = set()
|
||||
# Localize attributes:
|
||||
get_attrib_name = group_code_mapping.get
|
||||
append_unprocessed_tag = unprocessed_tags.append
|
||||
unprotected_set_attrib = dxf.unprotected_set
|
||||
mark_attrib_as_processed = processed_names.add
|
||||
|
||||
# Ignore (100, "AcDb...") or (0, "ENTITY") tag in case of DXF R12
|
||||
start = 1 if tags[0].code in (0, 100) else 0
|
||||
for tag in tags[start:]:
|
||||
name = get_attrib_name(tag.code)
|
||||
if isinstance(name, list): # process group code duplicates:
|
||||
names = name
|
||||
# If all existing attrib names are used, treat this tag
|
||||
# like an unprocessed tag.
|
||||
name = None
|
||||
# The attribute names are processed in the order of their
|
||||
# definition:
|
||||
for name_ in names:
|
||||
if name_ not in processed_names:
|
||||
name = name_
|
||||
mark_attrib_as_processed(name_)
|
||||
break
|
||||
if name:
|
||||
# Ignore callback attributes and group codes explicit marked
|
||||
# as "*IGNORE":
|
||||
if name[0] != "*":
|
||||
unprotected_set_attrib(
|
||||
name, cast_value(tag.code, tag.value) # type: ignore
|
||||
)
|
||||
else:
|
||||
append_unprocessed_tag(tag)
|
||||
|
||||
if self.r12:
|
||||
# R12 has always unprocessed tags, because there are all tags in one
|
||||
# subclass and one subclass definition never covers all tags e.g.
|
||||
# handle is processed in DXFEntity, so it is an unprocessed tag in
|
||||
# AcDbEntity.
|
||||
return unprocessed_tags
|
||||
|
||||
# Only DXF R13+
|
||||
if recover and len(unprocessed_tags):
|
||||
# TODO: maybe obsolete if simple_dxfattribs_loader() is used for
|
||||
# most old DXF R12 entities
|
||||
unprocessed_tags = recover_graphic_attributes(unprocessed_tags, dxf)
|
||||
if len(unprocessed_tags) and log:
|
||||
# First tag is the subclass specifier (100, "AcDb...")
|
||||
name = tags[0].value
|
||||
self.log_unprocessed_tags(
|
||||
unprocessed_tags, subclass=name, handle=dxf.get("handle")
|
||||
)
|
||||
return unprocessed_tags
|
||||
|
||||
def append_base_class_to_acdb_entity(self) -> None:
|
||||
"""It is valid to mix up the base class with AcDbEntity class.
|
||||
This method appends all none base class group codes to the
|
||||
AcDbEntity class.
|
||||
"""
|
||||
# This is only needed for DXFEntity, so applying this method
|
||||
# automatically to all entities is waste of runtime
|
||||
# -> DXFGraphic.load_dxf_attribs()
|
||||
# TODO: maybe obsolete if simple_dxfattribs_loader() is used for
|
||||
# most old DXF R12 entities
|
||||
if self.r12:
|
||||
return
|
||||
|
||||
acdb_entity_tags = self.subclasses[1]
|
||||
if acdb_entity_tags[0] == (100, "AcDbEntity"):
|
||||
acdb_entity_tags.extend(
|
||||
tag for tag in self.subclasses[0] if tag.code not in BASE_CLASS_CODES
|
||||
)
|
||||
|
||||
def simple_dxfattribs_loader(
|
||||
self, dxf: DXFNamespace, group_code_mapping: dict[int, str]
|
||||
) -> None:
|
||||
# tested in test suite 201 for the POINT entity
|
||||
"""Load DXF attributes from all subclasses into the DXF namespace.
|
||||
|
||||
Can not handle same group codes in different subclasses, does not remove
|
||||
processed tags or log unprocessed tags and bypasses the DXF attribute
|
||||
validity checks.
|
||||
|
||||
This method ignores the subclass structure and can load data from
|
||||
very malformed DXF files, like such in issue #604.
|
||||
This method works only for very simple DXF entities with unique group
|
||||
codes in all subclasses, the old DXF R12 entities:
|
||||
|
||||
- POINT
|
||||
- LINE
|
||||
- CIRCLE
|
||||
- ARC
|
||||
- INSERT
|
||||
- SHAPE
|
||||
- SOLID/TRACE/3DFACE
|
||||
- TEXT (ATTRIB/ATTDEF bypasses TEXT loader)
|
||||
- BLOCK/ENDBLK
|
||||
- POLYLINE/VERTEX/SEQEND
|
||||
- DIMENSION and subclasses
|
||||
- all table entries: LAYER, LTYPE, ...
|
||||
|
||||
And the newer DXF entities:
|
||||
|
||||
- ELLIPSE
|
||||
- RAY/XLINE
|
||||
|
||||
The recover mode for graphical attributes is automatically included.
|
||||
Logging of unprocessed tags is not possible but also not required for
|
||||
this simple and well known entities.
|
||||
|
||||
Args:
|
||||
dxf: entity DXF namespace
|
||||
group_code_mapping: group code name mapping for all DXF attributes
|
||||
from all subclasses, callback attributes have to be marked with
|
||||
a leading "*"
|
||||
|
||||
"""
|
||||
tags = itertools.chain.from_iterable(self.subclasses)
|
||||
get_attrib_name = group_code_mapping.get
|
||||
unprotected_set_attrib = dxf.unprotected_set
|
||||
for tag in tags:
|
||||
name = get_attrib_name(tag.code)
|
||||
if isinstance(name, str) and not name.startswith("*"):
|
||||
unprotected_set_attrib(name, cast_value(tag.code, tag.value))
|
||||
|
||||
|
||||
GRAPHIC_ATTRIBUTES_TO_RECOVER = {
|
||||
8: "layer",
|
||||
6: "linetype",
|
||||
62: "color",
|
||||
67: "paperspace",
|
||||
370: "lineweight",
|
||||
48: "ltscale",
|
||||
60: "invisible",
|
||||
420: "true_color",
|
||||
430: "color_name",
|
||||
440: "transparency",
|
||||
284: "shadow_mode",
|
||||
347: "material_handle",
|
||||
348: "visualstyle_handle",
|
||||
380: "plotstyle_enum",
|
||||
390: "plotstyle_handle",
|
||||
}
|
||||
|
||||
|
||||
# TODO: maybe obsolete if simple_dxfattribs_loader() is used for
|
||||
# most old DXF R12 entities
|
||||
def recover_graphic_attributes(tags: Tags, dxf: DXFNamespace) -> Tags:
|
||||
unprocessed_tags = Tags()
|
||||
for tag in tags:
|
||||
attrib_name = GRAPHIC_ATTRIBUTES_TO_RECOVER.get(tag.code)
|
||||
# Don't know if the unprocessed tag is really a misplaced tag,
|
||||
# so check if the attribute already exist!
|
||||
if attrib_name and not dxf.hasattr(attrib_name):
|
||||
dxf.set(attrib_name, tag.value)
|
||||
else:
|
||||
unprocessed_tags.append(tag)
|
||||
return unprocessed_tags
|
||||
Reference in New Issue
Block a user