refactor: excel parse
This commit is contained in:
@@ -0,0 +1,760 @@
|
||||
# Copyright (c) 2019-2024 Manfred Moitzi
|
||||
# License: MIT License
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Iterable,
|
||||
cast,
|
||||
Optional,
|
||||
Callable,
|
||||
Union,
|
||||
Type,
|
||||
)
|
||||
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.const import SUBCLASS_MARKER, DXF2000, DXF2010
|
||||
from ezdxf.math import Vec3, Vec2, BoundingBox2d, UVec, Matrix44
|
||||
from .dxfentity import base_class, SubclassProcessor
|
||||
from .dxfgfx import DXFGraphic, acdb_entity
|
||||
from .dxfobj import DXFObject
|
||||
from .factory import register_entity
|
||||
from .copy import default_copy
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.audit import Auditor
|
||||
from ezdxf.entities import DXFNamespace, DXFEntity, Dictionary
|
||||
from ezdxf.lldxf.tagwriter import AbstractTagWriter
|
||||
from ezdxf.lldxf.types import DXFTag
|
||||
from ezdxf.document import Drawing
|
||||
from ezdxf import xref
|
||||
|
||||
__all__ = ["Image", "ImageDef", "ImageDefReactor", "RasterVariables", "Wipeout"]
|
||||
logger = logging.getLogger("ezdxf")
|
||||
|
||||
|
||||
class ImageBase(DXFGraphic):
|
||||
"""DXF IMAGE entity"""
|
||||
|
||||
DXFTYPE = "IMAGEBASE"
|
||||
_CLS_GROUP_CODES: dict[int, Union[str, list[str]]] = dict()
|
||||
_SUBCLASS_NAME = "dummy"
|
||||
MIN_DXF_VERSION_FOR_EXPORT = DXF2000
|
||||
|
||||
SHOW_IMAGE = 1
|
||||
SHOW_IMAGE_WHEN_NOT_ALIGNED = 2
|
||||
USE_CLIPPING_BOUNDARY = 4
|
||||
USE_TRANSPARENCY = 8
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
# Boundary/Clipping path coordinates:
|
||||
# 0/0 is in the Left/Top corner of the image!
|
||||
# x-coordinates increases in u_pixel vector direction
|
||||
# y-coordinates increases against the v_pixel vector!
|
||||
# see also WCS coordinate calculation
|
||||
self._boundary_path: list[Vec2] = []
|
||||
|
||||
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
|
||||
assert isinstance(entity, ImageBase)
|
||||
entity._boundary_path = list(self._boundary_path)
|
||||
|
||||
def post_new_hook(self) -> None:
|
||||
super().post_new_hook()
|
||||
self.reset_boundary_path()
|
||||
|
||||
def load_dxf_attribs(
|
||||
self, processor: Optional[SubclassProcessor] = None
|
||||
) -> DXFNamespace:
|
||||
dxf = super().load_dxf_attribs(processor)
|
||||
if processor:
|
||||
path_tags = processor.subclasses[2].pop_tags(codes=(14,))
|
||||
self.load_boundary_path(path_tags)
|
||||
processor.fast_load_dxfattribs(dxf, self._CLS_GROUP_CODES, 2, recover=True)
|
||||
if len(self.boundary_path) < 2: # something is wrong
|
||||
self.dxf = dxf
|
||||
self.reset_boundary_path()
|
||||
return dxf
|
||||
|
||||
def load_boundary_path(self, tags: Iterable[DXFTag]):
|
||||
self._boundary_path = [Vec2(value) for code, value in tags if code == 14]
|
||||
|
||||
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
||||
"""Export entity specific data as DXF tags."""
|
||||
super().export_entity(tagwriter)
|
||||
tagwriter.write_tag2(SUBCLASS_MARKER, self._SUBCLASS_NAME)
|
||||
self.dxf.count_boundary_points = len(self.boundary_path)
|
||||
self.dxf.export_dxf_attribs(
|
||||
tagwriter,
|
||||
[
|
||||
"class_version",
|
||||
"insert",
|
||||
"u_pixel",
|
||||
"v_pixel",
|
||||
"image_size",
|
||||
"image_def_handle",
|
||||
"flags",
|
||||
"clipping",
|
||||
"brightness",
|
||||
"contrast",
|
||||
"fade",
|
||||
"image_def_reactor_handle",
|
||||
"clipping_boundary_type",
|
||||
"count_boundary_points",
|
||||
],
|
||||
)
|
||||
self.export_boundary_path(tagwriter)
|
||||
if tagwriter.dxfversion >= DXF2010:
|
||||
self.dxf.export_dxf_attribs(tagwriter, "clip_mode")
|
||||
|
||||
def export_boundary_path(self, tagwriter: AbstractTagWriter):
|
||||
for vertex in self.boundary_path:
|
||||
tagwriter.write_vertex(14, vertex)
|
||||
|
||||
@property
|
||||
def boundary_path(self):
|
||||
"""Returns the boundray path in raw form in pixel coordinates.
|
||||
|
||||
A list of vertices as pixel coordinates, Two vertices describe a
|
||||
rectangle, lower left corner is (-0.5, -0.5) and upper right corner
|
||||
is (ImageSizeX-0.5, ImageSizeY-0.5), more than two vertices is a
|
||||
polygon as clipping path. All vertices as pixel coordinates. (read/write)
|
||||
"""
|
||||
return self._boundary_path
|
||||
|
||||
@boundary_path.setter
|
||||
def boundary_path(self, vertices: Iterable[UVec]) -> None:
|
||||
self.set_boundary_path(vertices)
|
||||
|
||||
def set_boundary_path(self, vertices: Iterable[UVec]) -> None:
|
||||
"""Set boundary path to `vertices`. Two vertices describe a rectangle
|
||||
(lower left and upper right corner), more than two vertices is a polygon
|
||||
as clipping path.
|
||||
"""
|
||||
_vertices = Vec2.list(vertices)
|
||||
if len(_vertices):
|
||||
if len(_vertices) > 2 and not _vertices[-1].isclose(_vertices[0]):
|
||||
# Close path, otherwise AutoCAD crashes
|
||||
_vertices.append(_vertices[0])
|
||||
self._boundary_path = _vertices
|
||||
self.set_flag_state(self.USE_CLIPPING_BOUNDARY, state=True)
|
||||
self.dxf.clipping = 1
|
||||
self.dxf.clipping_boundary_type = 1 if len(_vertices) < 3 else 2
|
||||
self.dxf.count_boundary_points = len(self._boundary_path)
|
||||
else:
|
||||
self.reset_boundary_path()
|
||||
|
||||
def reset_boundary_path(self) -> None:
|
||||
"""Reset boundary path to the default rectangle [(-0.5, -0.5),
|
||||
(ImageSizeX-0.5, ImageSizeY-0.5)].
|
||||
"""
|
||||
lower_left_corner = Vec2(-0.5, -0.5)
|
||||
upper_right_corner = Vec2(self.dxf.image_size) + lower_left_corner
|
||||
self._boundary_path = [lower_left_corner, upper_right_corner]
|
||||
self.set_flag_state(Image.USE_CLIPPING_BOUNDARY, state=False)
|
||||
self.dxf.clipping = 0
|
||||
self.dxf.clipping_boundary_type = 1
|
||||
self.dxf.count_boundary_points = 2
|
||||
|
||||
def transform(self, m: Matrix44) -> Self:
|
||||
"""Transform IMAGE entity by transformation matrix `m` inplace."""
|
||||
self.dxf.insert = m.transform(self.dxf.insert)
|
||||
self.dxf.u_pixel = m.transform_direction(self.dxf.u_pixel)
|
||||
self.dxf.v_pixel = m.transform_direction(self.dxf.v_pixel)
|
||||
self.post_transform(m)
|
||||
return self
|
||||
|
||||
def get_wcs_transform(self) -> Matrix44:
|
||||
m = Matrix44()
|
||||
m.set_row(0, Vec3(self.dxf.u_pixel))
|
||||
m.set_row(1, Vec3(self.dxf.v_pixel))
|
||||
m.set_row(3, Vec3(self.dxf.insert))
|
||||
return m
|
||||
|
||||
def pixel_boundary_path(self) -> list[Vec2]:
|
||||
"""Returns the boundary path as closed loop in pixel coordinates. Resolves the
|
||||
simple form of two vertices as a rectangle. The image coordinate system has an
|
||||
inverted y-axis and the top-left corner is (0, 0).
|
||||
|
||||
.. versionchanged:: 1.2.0
|
||||
|
||||
renamed from :meth:`boundray_path_ocs()`
|
||||
|
||||
"""
|
||||
boundary_path = self.boundary_path
|
||||
if len(boundary_path) == 2: # rectangle
|
||||
p0, p1 = boundary_path
|
||||
boundary_path = [p0, Vec2(p1.x, p0.y), p1, Vec2(p0.x, p1.y)]
|
||||
if not boundary_path[0].isclose(boundary_path[-1]):
|
||||
boundary_path.append(boundary_path[0])
|
||||
return boundary_path
|
||||
|
||||
def boundary_path_wcs(self) -> list[Vec3]:
|
||||
"""Returns the boundary/clipping path in WCS coordinates.
|
||||
|
||||
It's recommended to acquire the clipping path as :class:`~ezdxf.path.Path` object
|
||||
by the :func:`~ezdxf.path.make_path` function::
|
||||
|
||||
from ezdxf.path import make_path
|
||||
|
||||
image = ... # get image entity
|
||||
clipping_path = make_path(image)
|
||||
|
||||
"""
|
||||
|
||||
u = Vec3(self.dxf.u_pixel)
|
||||
v = Vec3(self.dxf.v_pixel)
|
||||
origin = Vec3(self.dxf.insert)
|
||||
origin += u * 0.5 - v * 0.5
|
||||
height = self.dxf.image_size.y
|
||||
# Boundary/Clipping path origin 0/0 is in the Left/Top corner of the image!
|
||||
vertices = [
|
||||
origin + (u * p.x) + (v * (height - p.y))
|
||||
for p in self.pixel_boundary_path()
|
||||
]
|
||||
return vertices
|
||||
|
||||
def destroy(self) -> None:
|
||||
if not self.is_alive:
|
||||
return
|
||||
|
||||
del self._boundary_path
|
||||
super().destroy()
|
||||
|
||||
|
||||
acdb_image = DefSubclass(
|
||||
"AcDbRasterImage",
|
||||
{
|
||||
"class_version": DXFAttr(90, dxfversion=DXF2000, default=0),
|
||||
"insert": DXFAttr(10, xtype=XType.point3d),
|
||||
# U-vector of a single pixel (points along the visual bottom of the image,
|
||||
# starting at the insertion point)
|
||||
"u_pixel": DXFAttr(11, xtype=XType.point3d),
|
||||
# V-vector of a single pixel (points along the visual left side of the
|
||||
# image, starting at the insertion point)
|
||||
"v_pixel": DXFAttr(12, xtype=XType.point3d),
|
||||
# Image size in pixels
|
||||
"image_size": DXFAttr(13, xtype=XType.point2d),
|
||||
# Hard reference to image def object
|
||||
"image_def_handle": DXFAttr(340),
|
||||
# Image display properties:
|
||||
# 1 = Show image
|
||||
# 2 = Show image when not aligned with screen
|
||||
# 4 = Use clipping boundary
|
||||
# 8 = Transparency is on
|
||||
"flags": DXFAttr(70, default=3),
|
||||
# Clipping state:
|
||||
# 0 = Off
|
||||
# 1 = On
|
||||
"clipping": DXFAttr(
|
||||
280,
|
||||
default=0,
|
||||
validator=validator.is_integer_bool,
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
# Brightness value (0-100; default = 50)
|
||||
"brightness": DXFAttr(
|
||||
281,
|
||||
default=50,
|
||||
validator=validator.is_in_integer_range(0, 101),
|
||||
fixer=validator.fit_into_integer_range(0, 101),
|
||||
),
|
||||
# Contrast value (0-100; default = 50)
|
||||
"contrast": DXFAttr(
|
||||
282,
|
||||
default=50,
|
||||
validator=validator.is_in_integer_range(0, 101),
|
||||
fixer=validator.fit_into_integer_range(0, 101),
|
||||
),
|
||||
# Fade value (0-100; default = 0)
|
||||
"fade": DXFAttr(
|
||||
283,
|
||||
default=0,
|
||||
validator=validator.is_in_integer_range(0, 101),
|
||||
fixer=validator.fit_into_integer_range(0, 101),
|
||||
),
|
||||
# Hard reference to image def reactor object, not required by AutoCAD
|
||||
"image_def_reactor_handle": DXFAttr(360),
|
||||
# Clipping boundary type:
|
||||
# 1 = Rectangular
|
||||
# 2 = Polygonal
|
||||
"clipping_boundary_type": DXFAttr(
|
||||
71,
|
||||
default=1,
|
||||
validator=validator.is_one_of({1, 2}),
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
# Number of clip boundary vertices that follow
|
||||
"count_boundary_points": DXFAttr(91),
|
||||
# Clip mode:
|
||||
# 0 = outside
|
||||
# 1 = inside mode
|
||||
"clip_mode": DXFAttr(
|
||||
290,
|
||||
dxfversion=DXF2010,
|
||||
default=0,
|
||||
validator=validator.is_integer_bool,
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
# boundary path coordinates are pixel coordinates NOT drawing units
|
||||
},
|
||||
)
|
||||
acdb_image_group_codes = group_code_mapping(acdb_image)
|
||||
|
||||
|
||||
@register_entity
|
||||
class Image(ImageBase):
|
||||
"""DXF IMAGE entity"""
|
||||
|
||||
DXFTYPE = "IMAGE"
|
||||
DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_image)
|
||||
_CLS_GROUP_CODES = acdb_image_group_codes
|
||||
_SUBCLASS_NAME = acdb_image.name # type: ignore
|
||||
DEFAULT_ATTRIBS = {"layer": "0", "flags": 3}
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._boundary_path: list[Vec2] = []
|
||||
self._image_def: Optional[ImageDef] = None
|
||||
self._image_def_reactor: Optional[ImageDefReactor] = None
|
||||
|
||||
@classmethod
|
||||
def new(
|
||||
cls: Type[Image],
|
||||
handle: Optional[str] = None,
|
||||
owner: Optional[str] = None,
|
||||
dxfattribs: Optional[dict] = None,
|
||||
doc: Optional[Drawing] = None,
|
||||
) -> Image:
|
||||
dxfattribs = dxfattribs or {}
|
||||
# 'image_def' is not a real DXF attribute (image_def_handle)
|
||||
image_def = dxfattribs.pop("image_def", None)
|
||||
image_size = (1, 1)
|
||||
if image_def and image_def.is_alive:
|
||||
image_size = image_def.dxf.image_size
|
||||
dxfattribs.setdefault("image_size", image_size)
|
||||
|
||||
image = cast("Image", super().new(handle, owner, dxfattribs, doc))
|
||||
image.image_def = image_def
|
||||
return image
|
||||
|
||||
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
|
||||
assert isinstance(entity, Image)
|
||||
super().copy_data(entity, copy_strategy=copy_strategy)
|
||||
# Each IMAGE has its own ImageDefReactor object, which will be created by
|
||||
# binding the copy to the document.
|
||||
entity.dxf.discard("image_def_reactor_handle")
|
||||
entity._image_def_reactor = None
|
||||
# shared IMAGE_DEF
|
||||
entity._image_def = self._image_def
|
||||
|
||||
def post_bind_hook(self) -> None:
|
||||
# Document in LOAD process -> post_load_hook()
|
||||
if self.doc.is_loading: # type: ignore
|
||||
return
|
||||
if self._image_def_reactor: # ImageDefReactor already exist
|
||||
return
|
||||
# The new Image was created by ezdxf and the ImageDefReactor
|
||||
# object does not exist:
|
||||
self._create_image_def_reactor()
|
||||
|
||||
def post_load_hook(self, doc: Drawing) -> Optional[Callable]:
|
||||
super().post_load_hook(doc)
|
||||
db = doc.entitydb
|
||||
self._image_def = db.get(self.dxf.get("image_def_handle", None)) # type: ignore
|
||||
if self._image_def is None:
|
||||
# unrecoverable structure error
|
||||
self.destroy()
|
||||
return None
|
||||
|
||||
self._image_def_reactor = db.get( # type: ignore
|
||||
self.dxf.get("image_def_reactor_handle", None)
|
||||
)
|
||||
if self._image_def_reactor is None:
|
||||
# Image and ImageDef exist - this is recoverable by creating
|
||||
# an ImageDefReactor, but the objects section does not exist yet!
|
||||
# Return a post init command:
|
||||
return self._fix_missing_image_def_reactor
|
||||
return None
|
||||
|
||||
def _fix_missing_image_def_reactor(self):
|
||||
try:
|
||||
self._create_image_def_reactor()
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"An exception occurred while executing fixing command for "
|
||||
f"{str(self)}, destroying entity.",
|
||||
exc_info=e,
|
||||
)
|
||||
self.destroy()
|
||||
return
|
||||
logger.debug(f"Created missing ImageDefReactor for {str(self)}")
|
||||
|
||||
def _create_image_def_reactor(self):
|
||||
# ImageDef -> ImageDefReactor -> Image
|
||||
image_def_reactor = self.doc.objects.add_image_def_reactor(self.dxf.handle)
|
||||
reactor_handle = image_def_reactor.dxf.handle
|
||||
# Link Image to ImageDefReactor:
|
||||
self.dxf.image_def_reactor_handle = reactor_handle
|
||||
self._image_def_reactor = image_def_reactor
|
||||
# Link ImageDef to ImageDefReactor if in same document (XREF mapping!):
|
||||
if self.doc is self._image_def.doc:
|
||||
self._image_def.append_reactor_handle(reactor_handle)
|
||||
|
||||
def register_resources(self, registry: xref.Registry) -> None:
|
||||
"""Register required resources to the resource registry."""
|
||||
super().register_resources(registry)
|
||||
if isinstance(self.image_def, ImageDef):
|
||||
registry.add_handle(self.image_def.dxf.handle)
|
||||
|
||||
def map_resources(self, clone: Self, mapping: xref.ResourceMapper) -> None:
|
||||
"""Translate resources from self to the copied entity."""
|
||||
assert isinstance(clone, Image)
|
||||
super().map_resources(clone, mapping)
|
||||
source_image_def = self.image_def
|
||||
if isinstance(source_image_def, ImageDef):
|
||||
name = self.get_image_def_name()
|
||||
name, clone_image_def = mapping.map_acad_dict_entry(
|
||||
"ACAD_IMAGE_DICT", name, source_image_def
|
||||
)
|
||||
if isinstance(clone_image_def, ImageDef):
|
||||
clone.image_def = clone_image_def
|
||||
if isinstance(clone._image_def_reactor, ImageDefReactor):
|
||||
clone_image_def.append_reactor_handle(
|
||||
clone._image_def_reactor.dxf.handle
|
||||
)
|
||||
# Note:
|
||||
# The IMAGEDEF_REACTOR was created automatically at binding the copy to
|
||||
# a new document, but the handle of the IMAGEDEF_REACTOR was not add to the
|
||||
# IMAGEDEF reactor handles, because at this point the IMAGE had still a reference
|
||||
# to the IMAGEDEF of the source document.
|
||||
|
||||
def get_image_def_name(self) -> str:
|
||||
"""Returns the name of the `image_def` entry in the ACAD_IMAGE_DICT."""
|
||||
if self.doc is None:
|
||||
return ""
|
||||
image_dict = self.doc.rootdict.get_required_dict("ACAD_IMAGE_DICT")
|
||||
for name, entry in image_dict.items():
|
||||
if entry is self._image_def:
|
||||
return name
|
||||
return ""
|
||||
|
||||
@property
|
||||
def image_def(self) -> Optional[ImageDef]:
|
||||
"""Returns the associated IMAGEDEF entity, see :class:`ImageDef`."""
|
||||
return self._image_def
|
||||
|
||||
@image_def.setter
|
||||
def image_def(self, image_def: ImageDef) -> None:
|
||||
if image_def and image_def.is_alive:
|
||||
self.dxf.image_def_handle = image_def.dxf.handle
|
||||
self._image_def = image_def
|
||||
else:
|
||||
self.dxf.discard("image_def_handle")
|
||||
self._image_def = None
|
||||
|
||||
@property
|
||||
def image_def_reactor(self) -> Optional[ImageDefReactor]:
|
||||
"""Returns the associated IMAGEDEF_REACTOR entity."""
|
||||
return self._image_def_reactor
|
||||
|
||||
def destroy(self) -> None:
|
||||
if not self.is_alive:
|
||||
return
|
||||
|
||||
reactor = self._image_def_reactor
|
||||
if reactor and reactor.is_alive:
|
||||
image_def = self.image_def
|
||||
if image_def and image_def.is_alive:
|
||||
image_def.discard_reactor_handle(reactor.dxf.handle)
|
||||
reactor.destroy()
|
||||
super().destroy()
|
||||
|
||||
def audit(self, auditor: Auditor) -> None:
|
||||
super().audit(auditor)
|
||||
|
||||
|
||||
# DXF reference error: Subclass marker (AcDbRasterImage)
|
||||
acdb_wipeout = DefSubclass("AcDbWipeout", dict(acdb_image.attribs))
|
||||
acdb_wipeout_group_codes = group_code_mapping(acdb_wipeout)
|
||||
|
||||
|
||||
@register_entity
|
||||
class Wipeout(ImageBase):
|
||||
"""DXF WIPEOUT entity"""
|
||||
|
||||
DXFTYPE = "WIPEOUT"
|
||||
DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_wipeout)
|
||||
DEFAULT_ATTRIBS = {
|
||||
"layer": "0",
|
||||
"flags": 7,
|
||||
"clipping": 1,
|
||||
"brightness": 50,
|
||||
"contrast": 50,
|
||||
"fade": 0,
|
||||
"image_size": (1, 1),
|
||||
"image_def_handle": "0", # has no ImageDef()
|
||||
"image_def_reactor_handle": "0", # has no ImageDefReactor()
|
||||
"clip_mode": 0,
|
||||
}
|
||||
_CLS_GROUP_CODES = acdb_wipeout_group_codes
|
||||
_SUBCLASS_NAME = acdb_wipeout.name # type: ignore
|
||||
|
||||
def set_masking_area(self, vertices: Iterable[UVec]) -> None:
|
||||
"""Set a new masking area, the area is placed in the layout xy-plane."""
|
||||
self.update_dxf_attribs(self.DEFAULT_ATTRIBS)
|
||||
vertices = Vec2.list(vertices)
|
||||
bounds = BoundingBox2d(vertices)
|
||||
x_size, y_size = bounds.size
|
||||
|
||||
dxf = self.dxf
|
||||
dxf.insert = Vec3(bounds.extmin)
|
||||
dxf.u_pixel = Vec3(x_size, 0, 0)
|
||||
dxf.v_pixel = Vec3(0, y_size, 0)
|
||||
|
||||
def boundary_path():
|
||||
extmin = bounds.extmin
|
||||
for vertex in vertices:
|
||||
v = vertex - extmin
|
||||
yield Vec2(v.x / x_size - 0.5, 0.5 - v.y / y_size)
|
||||
|
||||
self.set_boundary_path(boundary_path())
|
||||
|
||||
def _reset_handles(self):
|
||||
self.dxf.image_def_reactor_handle = "0"
|
||||
self.dxf.image_def_handle = "0"
|
||||
|
||||
def audit(self, auditor: Auditor) -> None:
|
||||
self._reset_handles()
|
||||
super().audit(auditor)
|
||||
|
||||
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
||||
"""Export entity specific data as DXF tags."""
|
||||
self._reset_handles()
|
||||
super().export_entity(tagwriter)
|
||||
|
||||
|
||||
# About Image File Paths:
|
||||
# See notes in knowledge graph: [[IMAGE File Paths]]
|
||||
# https://ezdxf.mozman.at/notes/#/page/image%20file%20paths
|
||||
|
||||
acdb_image_def = DefSubclass(
|
||||
"AcDbRasterImageDef",
|
||||
{
|
||||
"class_version": DXFAttr(90, default=0),
|
||||
# File name of image:
|
||||
"filename": DXFAttr(1),
|
||||
# Image size in pixels:
|
||||
"image_size": DXFAttr(10, xtype=XType.point2d),
|
||||
# Default size of one pixel in AutoCAD units:
|
||||
"pixel_size": DXFAttr(11, xtype=XType.point2d, default=Vec2(0.01, 0.01)),
|
||||
"loaded": DXFAttr(280, default=1),
|
||||
# Resolution units - this enums differ from the usual drawing units,
|
||||
# units.py, same as for RasterVariables.dxf.units, but only these 3 values
|
||||
# are valid - confirmed by ODA Specs 20.4.81 IMAGEDEF:
|
||||
# 0 = No units
|
||||
# 2 = Centimeters
|
||||
# 5 = Inch
|
||||
"resolution_units": DXFAttr(
|
||||
281,
|
||||
default=0,
|
||||
validator=validator.is_one_of({0, 2, 5}),
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
},
|
||||
)
|
||||
acdb_image_def_group_codes = group_code_mapping(acdb_image_def)
|
||||
|
||||
|
||||
@register_entity
|
||||
class ImageDef(DXFObject):
|
||||
"""DXF IMAGEDEF entity"""
|
||||
|
||||
DXFTYPE = "IMAGEDEF"
|
||||
DXFATTRIBS = DXFAttributes(base_class, acdb_image_def)
|
||||
MIN_DXF_VERSION_FOR_EXPORT = DXF2000
|
||||
|
||||
def load_dxf_attribs(
|
||||
self, processor: Optional[SubclassProcessor] = None
|
||||
) -> DXFNamespace:
|
||||
dxf = super().load_dxf_attribs(processor)
|
||||
if processor:
|
||||
processor.fast_load_dxfattribs(dxf, acdb_image_def_group_codes, 1)
|
||||
return dxf
|
||||
|
||||
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
||||
"""Export entity specific data as DXF tags."""
|
||||
super().export_entity(tagwriter)
|
||||
tagwriter.write_tag2(SUBCLASS_MARKER, acdb_image_def.name)
|
||||
self.dxf.export_dxf_attribs(
|
||||
tagwriter,
|
||||
[
|
||||
"class_version",
|
||||
"filename",
|
||||
"image_size",
|
||||
"pixel_size",
|
||||
"loaded",
|
||||
"resolution_units",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
acdb_image_def_reactor = DefSubclass(
|
||||
"AcDbRasterImageDefReactor",
|
||||
{
|
||||
"class_version": DXFAttr(90, default=2),
|
||||
# Handle to image:
|
||||
"image_handle": DXFAttr(330),
|
||||
},
|
||||
)
|
||||
acdb_image_def_reactor_group_codes = group_code_mapping(acdb_image_def_reactor)
|
||||
|
||||
|
||||
@register_entity
|
||||
class ImageDefReactor(DXFObject):
|
||||
"""DXF IMAGEDEF_REACTOR entity"""
|
||||
|
||||
DXFTYPE = "IMAGEDEF_REACTOR"
|
||||
DXFATTRIBS = DXFAttributes(base_class, acdb_image_def_reactor)
|
||||
MIN_DXF_VERSION_FOR_EXPORT = DXF2000
|
||||
|
||||
def load_dxf_attribs(
|
||||
self, processor: Optional[SubclassProcessor] = None
|
||||
) -> DXFNamespace:
|
||||
dxf = super().load_dxf_attribs(processor)
|
||||
if processor:
|
||||
processor.fast_load_dxfattribs(dxf, acdb_image_def_reactor_group_codes, 1)
|
||||
return dxf
|
||||
|
||||
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
||||
"""Export entity specific data as DXF tags."""
|
||||
super().export_entity(tagwriter)
|
||||
tagwriter.write_tag2(SUBCLASS_MARKER, acdb_image_def_reactor.name)
|
||||
tagwriter.write_tag2(90, self.dxf.class_version)
|
||||
tagwriter.write_tag2(330, self.dxf.image_handle)
|
||||
|
||||
|
||||
acdb_raster_variables = DefSubclass(
|
||||
"AcDbRasterVariables",
|
||||
{
|
||||
"class_version": DXFAttr(90, default=0),
|
||||
# Frame:
|
||||
# 0 = no frame
|
||||
# 1 = show frame
|
||||
"frame": DXFAttr(
|
||||
70,
|
||||
default=0,
|
||||
validator=validator.is_integer_bool,
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
# Quality:
|
||||
# 0 = draft
|
||||
# 1 = high
|
||||
"quality": DXFAttr(
|
||||
71,
|
||||
default=1,
|
||||
validator=validator.is_integer_bool,
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
# Units:
|
||||
# 0 = None
|
||||
# 1 = mm
|
||||
# 2 = cm
|
||||
# 3 = m
|
||||
# 4 = km
|
||||
# 5 = in
|
||||
# 6 = ft
|
||||
# 7 = yd
|
||||
# 8 = mi
|
||||
"units": DXFAttr(
|
||||
72,
|
||||
default=3,
|
||||
validator=validator.is_in_integer_range(0, 9),
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
},
|
||||
)
|
||||
acdb_raster_variables_group_codes = group_code_mapping(acdb_raster_variables)
|
||||
|
||||
|
||||
@register_entity
|
||||
class RasterVariables(DXFObject):
|
||||
"""DXF RASTERVARIABLES entity"""
|
||||
|
||||
DXFTYPE = "RASTERVARIABLES"
|
||||
DXFATTRIBS = DXFAttributes(base_class, acdb_raster_variables)
|
||||
MIN_DXF_VERSION_FOR_EXPORT = DXF2000
|
||||
|
||||
def load_dxf_attribs(
|
||||
self, processor: Optional[SubclassProcessor] = None
|
||||
) -> DXFNamespace:
|
||||
dxf = super().load_dxf_attribs(processor)
|
||||
if processor:
|
||||
processor.fast_load_dxfattribs(dxf, acdb_raster_variables_group_codes, 1)
|
||||
return dxf
|
||||
|
||||
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
||||
"""Export entity specific data as DXF tags."""
|
||||
super().export_entity(tagwriter)
|
||||
tagwriter.write_tag2(SUBCLASS_MARKER, acdb_raster_variables.name)
|
||||
self.dxf.export_dxf_attribs(
|
||||
tagwriter,
|
||||
[
|
||||
"class_version",
|
||||
"frame",
|
||||
"quality",
|
||||
"units",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
acdb_wipeout_variables = DefSubclass(
|
||||
"AcDbWipeoutVariables",
|
||||
{
|
||||
# Display-image-frame flag:
|
||||
# 0 = No frame
|
||||
# 1 = Display frame
|
||||
"frame": DXFAttr(
|
||||
70,
|
||||
default=0,
|
||||
validator=validator.is_integer_bool,
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
},
|
||||
)
|
||||
acdb_wipeout_variables_group_codes = group_code_mapping(acdb_wipeout_variables)
|
||||
|
||||
|
||||
@register_entity
|
||||
class WipeoutVariables(DXFObject):
|
||||
"""DXF WIPEOUTVARIABLES entity"""
|
||||
|
||||
DXFTYPE = "WIPEOUTVARIABLES"
|
||||
DXFATTRIBS = DXFAttributes(base_class, acdb_wipeout_variables)
|
||||
MIN_DXF_VERSION_FOR_EXPORT = DXF2000
|
||||
|
||||
def load_dxf_attribs(
|
||||
self, processor: Optional[SubclassProcessor] = None
|
||||
) -> DXFNamespace:
|
||||
dxf = super().load_dxf_attribs(processor)
|
||||
if processor:
|
||||
processor.fast_load_dxfattribs(dxf, acdb_wipeout_variables_group_codes, 1)
|
||||
return dxf
|
||||
|
||||
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
||||
"""Export entity specific data as DXF tags."""
|
||||
super().export_entity(tagwriter)
|
||||
tagwriter.write_tag2(SUBCLASS_MARKER, acdb_wipeout_variables.name)
|
||||
self.dxf.export_dxf_attribs(tagwriter, "frame")
|
||||
Reference in New Issue
Block a user