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

372 lines
11 KiB
Python

# Copyright (c) 2011-2022, Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import (
Any,
Iterable,
Iterator,
KeysView,
Optional,
Sequence,
TYPE_CHECKING,
Union,
)
import logging
from collections import OrderedDict
from ezdxf.lldxf import const
from ezdxf.lldxf.tags import group_tags, Tags, DXFTag
from ezdxf.lldxf.types import strtag
from ezdxf.lldxf.validator import header_validator
from ezdxf.sections.headervars import (
HEADER_VAR_MAP,
version_specific_group_code,
)
if TYPE_CHECKING:
from ezdxf.lldxf.tagwriter import AbstractTagWriter
logger = logging.getLogger("ezdxf")
MIN_HEADER_TEXT = """ 0
SECTION
2
HEADER
9
$ACADVER
1
AC1009
9
$DWGCODEPAGE
3
ANSI_1252
9
$HANDSEED
5
FF
"""
# Additional variables may be stored as DICTIONARYVAR in the OBJECTS
# section in the DICTIONARY "AcDbVariableDictionary" of the root dict.
# - CANNOSCALE
# - CENTEREXE
# - CENTERLTYPEFILE
# - CETRANSPARENCY
# - CMLEADERSTYLE
# - CTABLESTYLE
# - CVIEWDETAILSTYLE
# - CVIEWSECTIONSTYLE
# - LAYEREVAL
# - LAYERNOTIFY
# - LIGHTINGUNITS
# - MSLTSCALE
class CustomVars:
"""The :class:`CustomVars` class stores custom properties in the DXF header as
$CUSTOMPROPERTYTAG and $CUSTOMPROPERTY values. Custom properties require DXF R2004
or later, `ezdxf` can create custom properties for older DXF versions as well, but
AutoCAD will not show that properties.
"""
def __init__(self) -> None:
self.properties: list[tuple[str, str]] = list()
def __len__(self) -> int:
"""Count of custom properties."""
return len(self.properties)
def __iter__(self) -> Iterator[tuple[str, str]]:
"""Iterate over all custom properties as ``(tag, value)`` tuples."""
return iter(self.properties)
def clear(self) -> None:
"""Remove all custom properties."""
self.properties.clear()
def append(self, tag: str, value: str) -> None:
"""Add custom property as ``(tag, value)`` tuple."""
# custom properties always stored as strings
self.properties.append((tag, str(value)))
def get(self, tag: str, default: Optional[str] = None):
"""Returns the value of the first custom property `tag`."""
for key, value in self.properties:
if key == tag:
return value
else:
return default
def has_tag(self, tag: str) -> bool:
"""Returns ``True`` if custom property `tag` exist."""
return self.get(tag) is not None
def remove(self, tag: str, all: bool = False) -> None:
"""Removes the first occurrence of custom property `tag`, removes all
occurrences if `all` is ``True``.
Raises `:class:`DXFValueError` if `tag` does not exist.
"""
found_tag = False
for item in self.properties:
if item[0] == tag:
self.properties.remove(item)
found_tag = True
if not all:
return
if not found_tag:
raise const.DXFValueError(f"Tag '{tag}' does not exist")
def replace(self, tag: str, value: str) -> None:
"""Replaces the value of the first custom property `tag` by a new
`value`.
Raises :class:`DXFValueError` if `tag` does not exist.
"""
properties = self.properties
for index in range(len(properties)):
name = properties[index][0]
if name == tag:
properties[index] = (name, value)
return
raise const.DXFValueError(f"Tag '{tag}' does not exist")
def write(self, tagwriter: AbstractTagWriter) -> None:
"""Export custom properties as DXF tags. (internal API)"""
for tag, value in self.properties:
s = f" 9\n$CUSTOMPROPERTYTAG\n 1\n{tag}\n 9\n$CUSTOMPROPERTY\n 1\n{value}\n"
tagwriter.write_str(s)
def default_vars() -> OrderedDict:
vars = OrderedDict()
for vardef in HEADER_VAR_MAP.values():
vars[vardef.name] = HeaderVar(DXFTag(vardef.code, vardef.default))
return vars
class HeaderSection:
MIN_HEADER_TAGS = Tags.from_text(MIN_HEADER_TEXT)
name = "HEADER"
def __init__(self) -> None:
self.hdrvars: dict[str, HeaderVar] = OrderedDict()
self.custom_vars = CustomVars()
@classmethod
def load(cls, tags: Optional[Iterable[DXFTag]] = None) -> HeaderSection:
"""Constructor to generate header variables loaded from DXF files
(untrusted environment).
Args:
tags: DXF tags as Tags() or ExtendedTags()
(internal API)
"""
if tags is None: # create default header
# files without a header have the default version: R12
return cls.new(dxfversion=const.DXF12)
section = cls()
section.load_tags(iter(tags))
return section
@classmethod
def new(cls, dxfversion=const.LATEST_DXF_VERSION) -> HeaderSection:
section = HeaderSection()
section.hdrvars = default_vars()
section["$ACADVER"] = dxfversion
return section
def load_tags(self, tags: Iterable[DXFTag]) -> None:
"""Constructor to generate header variables loaded from DXF files
(untrusted environment).
Args:
tags: DXF tags as Tags() or ExtendedTags()
"""
_tags = iter(tags) or iter(self.MIN_HEADER_TAGS)
section_tag = next(_tags)
name_tag = next(_tags)
if section_tag != (0, "SECTION") or name_tag != (2, "HEADER"):
raise const.DXFStructureError("Critical structure error in HEADER section.")
groups = group_tags(header_validator(_tags), splitcode=9)
custom_property_stack = [] # collect $CUSTOMPROPERTY/TAG
for group in groups:
name = group[0].value
value = group[1]
if name in ("$CUSTOMPROPERTYTAG", "$CUSTOMPROPERTY"):
custom_property_stack.append(value.value)
else:
self.hdrvars[name] = HeaderVar(value)
custom_property_stack.reverse()
while len(custom_property_stack):
try:
self.custom_vars.append(
tag=custom_property_stack.pop(),
value=custom_property_stack.pop(),
)
except IndexError: # internal exception
break
@classmethod
def from_text(cls, text: str) -> HeaderSection:
"""Load constructor from text for testing"""
return cls.load(Tags.from_text(text))
def _headervar_factory(self, key: str, value: Any) -> DXFTag:
if key in HEADER_VAR_MAP:
factory = HEADER_VAR_MAP[key].factory
return factory(value)
else:
raise const.DXFKeyError(f"Invalid header variable {key}.")
def __len__(self) -> int:
"""Returns count of header variables."""
return len(self.hdrvars)
def __contains__(self, key) -> bool:
"""Returns ``True`` if header variable `key` exist."""
return key in self.hdrvars
def varnames(self) -> KeysView:
"""Returns an iterable of all header variable names."""
return self.hdrvars.keys()
def export_dxf(self, tagwriter: AbstractTagWriter) -> None:
"""Exports header section as DXF tags. (internal API)"""
def _write(name: str, value: Any) -> None:
if value.value is None:
logger.info(f"did not write header var {name}, value is None.")
return
tagwriter.write_tag2(9, name)
group_code = version_specific_group_code(name, dxfversion)
# fix invalid group codes
if group_code != value.code:
value = HeaderVar((group_code, value.value))
tagwriter.write_str(str(value))
dxfversion: str = tagwriter.dxfversion
write_handles = tagwriter.write_handles
tagwriter.write_str(" 0\nSECTION\n 2\nHEADER\n")
save = self["$ACADVER"]
self["$ACADVER"] = dxfversion
for name, value in header_vars_by_priority(self.hdrvars, dxfversion):
if not write_handles and name == "$HANDSEED":
continue # skip $HANDSEED
_write(name, value)
if name == "$LASTSAVEDBY": # ugly hack, but necessary for AutoCAD
self.custom_vars.write(tagwriter)
self["$ACADVER"] = save
tagwriter.write_str(" 0\nENDSEC\n")
def get(self, key: str, default: Any = None) -> Any:
"""Returns value of header variable `key` if exist, else the `default`
value.
"""
if key in self.hdrvars:
return self.__getitem__(key)
else:
return default
def __getitem__(self, key: str) -> Any:
"""Get header variable `key` by index operator like:
:code:`drawing.header['$ACADVER']`
"""
try:
return self.hdrvars[key].value
except KeyError: # map exception
raise const.DXFKeyError(str(key))
def __setitem__(self, key: str, value: Any) -> None:
"""Set header variable `key` to `value` by index operator like:
:code:`drawing.header['$ANGDIR'] = 1`
"""
try:
tags = self._headervar_factory(key, value)
except (IndexError, ValueError):
raise const.DXFValueError(str(value))
self.hdrvars[key] = HeaderVar(tags)
def __delitem__(self, key: str) -> None:
"""Delete header variable `key` by index operator like:
:code:`del drawing.header['$ANGDIR']`
"""
try:
del self.hdrvars[key]
except KeyError: # map exception
raise const.DXFKeyError(str(key))
def reset_wcs(self):
"""Reset the current UCS settings to the :ref:`WCS`."""
self["$UCSBASE"] = ""
self["$UCSNAME"] = ""
self["$UCSORG"] = (0, 0, 0)
self["$UCSXDIR"] = (1, 0, 0)
self["$UCSYDIR"] = (0, 1, 0)
self["$UCSORTHOREF"] = ""
self["$UCSORTHOVIEW"] = 0
self["$UCSORGTOP"] = (0, 0, 0)
self["$UCSORGBOTTOM"] = (0, 0, 0)
self["$UCSORGLEFT"] = (0, 0, 0)
self["$UCSORGRIGHT"] = (0, 0, 0)
self["$UCSORGFRONT"] = (0, 0, 0)
self["$UCSORGBACK"] = (0, 0, 0)
def header_vars_by_priority(
header_vars: dict[str, HeaderVar], dxfversion: str
) -> Iterable[tuple]:
order = []
for name, value in header_vars.items():
vardef = HEADER_VAR_MAP.get(name, None)
if vardef is None:
logger.info(f"Header variable {name} ignored, dxfversion={dxfversion}.")
continue
if vardef.mindxf <= dxfversion <= vardef.maxdxf:
order.append((vardef.priority, (name, value)))
order.sort()
for priority, tag in order:
yield tag
class HeaderVar:
def __init__(self, tag: Union[DXFTag, Sequence]):
self.tag = tag
@property
def code(self) -> int:
return self.tag[0]
@property
def value(self) -> Any:
return self.tag[1]
@property
def ispoint(self) -> bool:
return self.code == 10
def __str__(self) -> str:
if self.ispoint:
code, value = self.tag
s = []
for coord in value:
s.append(strtag((code, coord)))
code += 10
return "".join(s)
else:
return strtag(self.tag) # type: ignore