729 lines
26 KiB
Python
729 lines
26 KiB
Python
# Copyright (c) 2019-2024 Manfred Moitzi
|
|
# License: MIT License
|
|
from __future__ import annotations
|
|
from typing import TYPE_CHECKING, Optional, Iterable, Any
|
|
from typing_extensions import Self, TypeGuard
|
|
|
|
from ezdxf.entities import factory
|
|
from ezdxf import options
|
|
from ezdxf.lldxf import validator
|
|
from ezdxf.lldxf.attributes import (
|
|
DXFAttr,
|
|
DXFAttributes,
|
|
DefSubclass,
|
|
RETURN_DEFAULT,
|
|
group_code_mapping,
|
|
)
|
|
from ezdxf import colors as clr
|
|
from ezdxf.lldxf import const
|
|
from ezdxf.lldxf.const import (
|
|
DXF12,
|
|
DXF2000,
|
|
DXF2004,
|
|
DXF2007,
|
|
DXF2013,
|
|
SUBCLASS_MARKER,
|
|
TRANSPARENCY_BYBLOCK,
|
|
)
|
|
from ezdxf.math import OCS, Matrix44, UVec
|
|
from ezdxf.proxygraphic import load_proxy_graphic, export_proxy_graphic
|
|
from .dxfentity import DXFEntity, base_class, SubclassProcessor, DXFTagStorage
|
|
|
|
if TYPE_CHECKING:
|
|
from ezdxf.audit import Auditor
|
|
from ezdxf.document import Drawing
|
|
from ezdxf.entities import DXFNamespace
|
|
from ezdxf.layouts import BaseLayout
|
|
from ezdxf.lldxf.tagwriter import AbstractTagWriter
|
|
from ezdxf import xref
|
|
|
|
__all__ = [
|
|
"DXFGraphic",
|
|
"acdb_entity",
|
|
"acdb_entity_group_codes",
|
|
"SeqEnd",
|
|
"add_entity",
|
|
"replace_entity",
|
|
"elevation_to_z_axis",
|
|
"is_graphic_entity",
|
|
"get_font_name",
|
|
]
|
|
|
|
GRAPHIC_PROPERTIES = {
|
|
"layer",
|
|
"linetype",
|
|
"color",
|
|
"lineweight",
|
|
"ltscale",
|
|
"true_color",
|
|
"color_name",
|
|
"transparency",
|
|
}
|
|
|
|
acdb_entity: DefSubclass = DefSubclass(
|
|
"AcDbEntity",
|
|
{
|
|
# Layer name as string, no auto fix for invalid names!
|
|
"layer": DXFAttr(8, default="0", validator=validator.is_valid_layer_name),
|
|
# Linetype name as string, no auto fix for invalid names!
|
|
"linetype": DXFAttr(
|
|
6,
|
|
default="BYLAYER",
|
|
optional=True,
|
|
validator=validator.is_valid_table_name,
|
|
),
|
|
# ACI color index, BYBLOCK=0, BYLAYER=256, BYOBJECT=257:
|
|
"color": DXFAttr(
|
|
62,
|
|
default=256,
|
|
optional=True,
|
|
validator=validator.is_valid_aci_color,
|
|
fixer=RETURN_DEFAULT,
|
|
),
|
|
# modelspace=0, paperspace=1
|
|
"paperspace": DXFAttr(
|
|
67,
|
|
default=0,
|
|
optional=True,
|
|
validator=validator.is_integer_bool,
|
|
fixer=RETURN_DEFAULT,
|
|
),
|
|
# Lineweight in mm times 100 (e.g. 0.13mm = 13). Smallest line weight is 13
|
|
# and biggest line weight is 200, values outside this range prevents AutoCAD
|
|
# from loading the file.
|
|
# Special values: BYLAYER=-1, BYBLOCK=-2, DEFAULT=-3
|
|
"lineweight": DXFAttr(
|
|
370,
|
|
default=-1,
|
|
dxfversion=DXF2000,
|
|
optional=True,
|
|
validator=validator.is_valid_lineweight,
|
|
fixer=validator.fix_lineweight,
|
|
),
|
|
"ltscale": DXFAttr(
|
|
48,
|
|
default=1.0,
|
|
dxfversion=DXF2000,
|
|
optional=True,
|
|
validator=validator.is_positive,
|
|
fixer=RETURN_DEFAULT,
|
|
),
|
|
# visible=0, invisible=1
|
|
"invisible": DXFAttr(60, default=0, dxfversion=DXF2000, optional=True),
|
|
# True color as 0x00RRGGBB 24-bit value
|
|
# True color always overrides ACI "color"!
|
|
"true_color": DXFAttr(420, dxfversion=DXF2004, optional=True),
|
|
# Color name as string. Color books are stored in .stb config files?
|
|
"color_name": DXFAttr(430, dxfversion=DXF2004, optional=True),
|
|
# Transparency value 0x020000TT 0 = fully transparent / 255 = opaque
|
|
# Special value 0x01000000 == ByBlock
|
|
# unset value means ByLayer
|
|
"transparency": DXFAttr(
|
|
440,
|
|
dxfversion=DXF2004,
|
|
optional=True,
|
|
validator=validator.is_transparency,
|
|
),
|
|
# Shadow mode:
|
|
# 0 = Casts and receives shadows
|
|
# 1 = Casts shadows
|
|
# 2 = Receives shadows
|
|
# 3 = Ignores shadows
|
|
"shadow_mode": DXFAttr(284, dxfversion=DXF2007, optional=True),
|
|
"material_handle": DXFAttr(347, dxfversion=DXF2007, optional=True),
|
|
"visualstyle_handle": DXFAttr(348, dxfversion=DXF2007, optional=True),
|
|
# PlotStyleName type enum (AcDb::PlotStyleNameType). Stored and moved around
|
|
# as a 16-bit integer. Custom non-entity
|
|
"plotstyle_enum": DXFAttr(380, dxfversion=DXF2007, default=1, optional=True),
|
|
# Handle value of the PlotStyleName object, basically a hard pointer, but
|
|
# has a different range to make backward compatibility easier to deal with.
|
|
"plotstyle_handle": DXFAttr(390, dxfversion=DXF2007, optional=True),
|
|
# 92 or 160?: Number of bytes in the proxy entity graphics represented in
|
|
# the subsequent 310 groups, which are binary chunk records (optional)
|
|
# 310: Proxy entity graphics data (multiple lines; 256 characters max. per
|
|
# line) (optional), compiled by TagCompiler() to a DXFBinaryTag() objects
|
|
},
|
|
)
|
|
acdb_entity_group_codes = group_code_mapping(acdb_entity)
|
|
|
|
|
|
def elevation_to_z_axis(dxf: DXFNamespace, names: Iterable[str]):
|
|
# The elevation group code (38) is only used for DXF R11 and prior and
|
|
# ignored for DXF R2000 and later.
|
|
# DXF R12 and later store the entity elevation in the z-axis of the
|
|
# vertices, but AutoCAD supports elevation for R12 if no z-axis is present.
|
|
# DXF types with legacy elevation support:
|
|
# SOLID, TRACE, TEXT, CIRCLE, ARC, TEXT, ATTRIB, ATTDEF, INSERT, SHAPE
|
|
|
|
# The elevation is only used for DXF R12 if no z-axis is stored in the DXF
|
|
# file. This is a problem because ezdxf loads the vertices always as 3D
|
|
# vertex including a z-axis even if no z-axis is present in DXF file.
|
|
if dxf.hasattr("elevation"):
|
|
elevation = dxf.elevation
|
|
# ezdxf does not export the elevation attribute for any DXF version
|
|
dxf.discard("elevation")
|
|
if elevation == 0:
|
|
return
|
|
|
|
for name in names:
|
|
v = dxf.get(name)
|
|
# Only use elevation value if z-axis is 0, this will not work for
|
|
# situations where an elevation and a z-axis=0 is present, but let's
|
|
# assume if the elevation group code is used the z-axis is not
|
|
# present if z-axis is 0.
|
|
if v is not None and v.z == 0:
|
|
dxf.set(name, v.replace(z=elevation))
|
|
|
|
|
|
class DXFGraphic(DXFEntity):
|
|
"""Common base class for all graphic entities, a subclass of
|
|
:class:`~ezdxf.entities.dxfentity.DXFEntity`. These entities resides in
|
|
entity spaces like modelspace, paperspace or block.
|
|
"""
|
|
|
|
DXFTYPE = "DXFGFX"
|
|
DEFAULT_ATTRIBS: dict[str, Any] = {"layer": "0"}
|
|
DXFATTRIBS = DXFAttributes(base_class, acdb_entity)
|
|
|
|
def load_dxf_attribs(
|
|
self, processor: Optional[SubclassProcessor] = None
|
|
) -> DXFNamespace:
|
|
"""Adds subclass processing for 'AcDbEntity', requires previous base
|
|
class processing by parent class.
|
|
|
|
(internal API)
|
|
"""
|
|
# subclasses using simple_dxfattribs_loader() bypass this method!!!
|
|
dxf = super().load_dxf_attribs(processor)
|
|
if processor is None:
|
|
return dxf
|
|
r12 = processor.r12
|
|
# It is valid to mix up the base class with AcDbEntity class.
|
|
processor.append_base_class_to_acdb_entity()
|
|
|
|
# Load proxy graphic data if requested
|
|
if options.load_proxy_graphics:
|
|
# length tag has group code 92 until DXF R2010
|
|
if processor.dxfversion and processor.dxfversion < DXF2013:
|
|
code = 92
|
|
else:
|
|
code = 160
|
|
self.proxy_graphic = load_proxy_graphic(
|
|
processor.subclasses[0 if r12 else 1],
|
|
length_code=code,
|
|
)
|
|
processor.fast_load_dxfattribs(dxf, acdb_entity_group_codes, 1)
|
|
return dxf
|
|
|
|
def post_new_hook(self) -> None:
|
|
"""Post-processing and integrity validation after entity creation.
|
|
|
|
(internal API)
|
|
"""
|
|
if self.doc:
|
|
if self.dxf.linetype not in self.doc.linetypes:
|
|
raise const.DXFInvalidLineType(
|
|
f'Linetype "{self.dxf.linetype}" not defined.'
|
|
)
|
|
|
|
@property
|
|
def rgb(self) -> tuple[int, int, int] | None:
|
|
"""Returns RGB true color as (r, g, b) tuple or None if true_color is not set."""
|
|
if self.dxf.hasattr("true_color"):
|
|
return clr.int2rgb(self.dxf.get("true_color"))
|
|
return None
|
|
|
|
@rgb.setter
|
|
def rgb(self, rgb: clr.RGB | tuple[int, int, int]) -> None:
|
|
"""Set RGB true color as (r, g , b) tuple e.g. (12, 34, 56).
|
|
|
|
Raises:
|
|
TypeError: input value `rgb` has invalid type
|
|
"""
|
|
self.dxf.set("true_color", clr.rgb2int(rgb))
|
|
|
|
@rgb.deleter
|
|
def rgb(self) -> None:
|
|
"""Delete RGB true color value."""
|
|
self.dxf.discard("true_color")
|
|
|
|
@property
|
|
def transparency(self) -> float:
|
|
"""Get transparency as float value between 0 and 1, 0 is opaque and 1
|
|
is 100% transparent (invisible). Transparency by block returns always 0.
|
|
"""
|
|
if self.dxf.hasattr("transparency"):
|
|
value = self.dxf.get("transparency")
|
|
if validator.is_transparency(value):
|
|
if value & TRANSPARENCY_BYBLOCK: # just check flag state
|
|
return 0.0
|
|
return clr.transparency2float(value)
|
|
return 0.0
|
|
|
|
@transparency.setter
|
|
def transparency(self, transparency: float) -> None:
|
|
"""Set transparency as float value between 0 and 1, 0 is opaque and 1
|
|
is 100% transparent (invisible).
|
|
"""
|
|
self.dxf.set("transparency", clr.float2transparency(transparency))
|
|
|
|
@property
|
|
def is_transparency_by_layer(self) -> bool:
|
|
"""Returns ``True`` if entity inherits transparency from layer."""
|
|
return not self.dxf.hasattr("transparency")
|
|
|
|
@property
|
|
def is_transparency_by_block(self) -> bool:
|
|
"""Returns ``True`` if entity inherits transparency from block."""
|
|
return self.dxf.get("transparency", 0) == TRANSPARENCY_BYBLOCK
|
|
|
|
def graphic_properties(self) -> dict:
|
|
"""Returns the important common properties layer, color, linetype,
|
|
lineweight, ltscale, true_color and color_name as `dxfattribs` dict.
|
|
"""
|
|
attribs = dict()
|
|
for key in GRAPHIC_PROPERTIES:
|
|
if self.dxf.hasattr(key):
|
|
attribs[key] = self.dxf.get(key)
|
|
return attribs
|
|
|
|
def ocs(self) -> OCS:
|
|
"""Returns object coordinate system (:ref:`ocs`) for 2D entities like
|
|
:class:`Text` or :class:`Circle`, returns a pass-through OCS for
|
|
entities without OCS support.
|
|
"""
|
|
# extrusion is only defined for 2D entities like Text, Circle, ...
|
|
if self.dxf.is_supported("extrusion"):
|
|
extrusion = self.dxf.get("extrusion", default=(0, 0, 1))
|
|
return OCS(extrusion)
|
|
else:
|
|
return OCS()
|
|
|
|
def set_owner(self, owner: Optional[str], paperspace: int = 0) -> None:
|
|
"""Set owner attribute and paperspace flag. (internal API)"""
|
|
self.dxf.owner = owner
|
|
if paperspace:
|
|
self.dxf.paperspace = paperspace
|
|
else:
|
|
self.dxf.discard("paperspace")
|
|
|
|
def link_entity(self, entity: DXFEntity) -> None:
|
|
"""Store linked or attached entities. Same API for both types of
|
|
appended data, because entities with linked entities (POLYLINE, INSERT)
|
|
have no attached entities and vice versa.
|
|
|
|
(internal API)
|
|
"""
|
|
pass
|
|
|
|
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export entity specific data as DXF tags. (internal API)"""
|
|
# Base class export is done by parent class.
|
|
self.export_acdb_entity(tagwriter)
|
|
# XDATA and embedded objects export is also done by the parent class.
|
|
|
|
def export_acdb_entity(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export subclass 'AcDbEntity' as DXF tags. (internal API)"""
|
|
# Full control over tag order and YES, sometimes order matters
|
|
not_r12 = tagwriter.dxfversion > DXF12
|
|
if not_r12:
|
|
tagwriter.write_tag2(SUBCLASS_MARKER, acdb_entity.name)
|
|
|
|
self.dxf.export_dxf_attribs(
|
|
tagwriter,
|
|
[
|
|
"paperspace",
|
|
"layer",
|
|
"linetype",
|
|
"material_handle",
|
|
"color",
|
|
"lineweight",
|
|
"ltscale",
|
|
"invisible",
|
|
"true_color",
|
|
"color_name",
|
|
"transparency",
|
|
"plotstyle_enum",
|
|
"plotstyle_handle",
|
|
"shadow_mode",
|
|
"visualstyle_handle",
|
|
],
|
|
)
|
|
|
|
if self.proxy_graphic and not_r12 and options.store_proxy_graphics:
|
|
# length tag has group code 92 until DXF R2010
|
|
export_proxy_graphic(
|
|
self.proxy_graphic,
|
|
tagwriter=tagwriter,
|
|
length_code=(92 if tagwriter.dxfversion < DXF2013 else 160),
|
|
)
|
|
|
|
def get_layout(self) -> Optional[BaseLayout]:
|
|
"""Returns the owner layout or returns ``None`` if entity is not
|
|
assigned to any layout.
|
|
"""
|
|
if self.dxf.owner is None or self.doc is None: # unlinked entity
|
|
return None
|
|
try:
|
|
return self.doc.layouts.get_layout_by_key(self.dxf.owner)
|
|
except const.DXFKeyError:
|
|
pass
|
|
try:
|
|
return self.doc.blocks.get_block_layout_by_handle(self.dxf.owner)
|
|
except const.DXFTableEntryError:
|
|
return None
|
|
|
|
def unlink_from_layout(self) -> None:
|
|
"""
|
|
Unlink entity from associated layout. Does nothing if entity is already
|
|
unlinked.
|
|
|
|
It is more efficient to call the
|
|
:meth:`~ezdxf.layouts.BaseLayout.unlink_entity` method of the associated
|
|
layout, especially if you have to unlink more than one entity.
|
|
"""
|
|
if not self.is_alive:
|
|
raise TypeError("Can not unlink destroyed entity.")
|
|
|
|
if self.doc is None:
|
|
# no doc -> no layout
|
|
self.dxf.owner = None
|
|
return
|
|
|
|
layout = self.get_layout()
|
|
if layout:
|
|
layout.unlink_entity(self)
|
|
|
|
def move_to_layout(
|
|
self, layout: BaseLayout, source: Optional[BaseLayout] = None
|
|
) -> None:
|
|
"""
|
|
Move entity from model space or a paper space layout to another layout.
|
|
For block layout as source, the block layout has to be specified. Moving
|
|
between different DXF drawings is not supported.
|
|
|
|
Args:
|
|
layout: any layout (model space, paper space, block)
|
|
source: provide source layout, faster for DXF R12, if entity is
|
|
in a block layout
|
|
|
|
Raises:
|
|
DXFStructureError: for moving between different DXF drawings
|
|
"""
|
|
if source is None:
|
|
source = self.get_layout()
|
|
if source is None:
|
|
raise const.DXFValueError("Source layout for entity not found.")
|
|
source.move_to_layout(self, layout)
|
|
|
|
def copy_to_layout(self, layout: BaseLayout) -> Self:
|
|
"""
|
|
Copy entity to another `layout`, returns new created entity as
|
|
:class:`DXFEntity` object. Copying between different DXF drawings is
|
|
not supported.
|
|
|
|
Args:
|
|
layout: any layout (model space, paper space, block)
|
|
|
|
Raises:
|
|
DXFStructureError: for copying between different DXF drawings
|
|
"""
|
|
if self.doc != layout.doc:
|
|
raise const.DXFStructureError(
|
|
"Copying between different DXF drawings is not supported."
|
|
)
|
|
|
|
new_entity = self.copy()
|
|
layout.add_entity(new_entity)
|
|
return new_entity
|
|
|
|
def audit(self, auditor: Auditor) -> None:
|
|
"""Audit and repair graphical DXF entities.
|
|
|
|
.. important::
|
|
|
|
Do not delete entities while auditing process, because this
|
|
would alter the entity database while iterating, instead use::
|
|
|
|
auditor.trash(entity)
|
|
|
|
to delete invalid entities after auditing automatically.
|
|
"""
|
|
assert self.doc is auditor.doc, "Auditor for different DXF document."
|
|
if not self.is_alive:
|
|
return
|
|
|
|
super().audit(auditor)
|
|
auditor.check_owner_exist(self)
|
|
dxf = self.dxf
|
|
if dxf.hasattr("layer"):
|
|
auditor.check_for_valid_layer_name(self)
|
|
if dxf.hasattr("linetype"):
|
|
auditor.check_entity_linetype(self)
|
|
if dxf.hasattr("color"):
|
|
auditor.check_entity_color_index(self)
|
|
if dxf.hasattr("lineweight"):
|
|
auditor.check_entity_lineweight(self)
|
|
if dxf.hasattr("extrusion"):
|
|
auditor.check_extrusion_vector(self)
|
|
if dxf.hasattr("transparency"):
|
|
auditor.check_transparency(self)
|
|
|
|
def transform(self, m: Matrix44) -> Self:
|
|
"""Inplace transformation interface, returns `self` (floating interface).
|
|
|
|
Args:
|
|
m: 4x4 transformation matrix (:class:`ezdxf.math.Matrix44`)
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def post_transform(self, m: Matrix44) -> None:
|
|
"""Should be called if the main entity transformation was successful."""
|
|
if self.xdata is not None:
|
|
self.xdata.transform(m)
|
|
|
|
@property
|
|
def is_post_transform_required(self) -> bool:
|
|
"""Check if post transform call is required."""
|
|
return self.xdata is not None
|
|
|
|
def translate(self, dx: float, dy: float, dz: float) -> Self:
|
|
"""Translate entity inplace about `dx` in x-axis, `dy` in y-axis and
|
|
`dz` in z-axis, returns `self` (floating interface).
|
|
|
|
Basic implementation uses the :meth:`transform` interface, subclasses
|
|
may have faster implementations.
|
|
"""
|
|
return self.transform(Matrix44.translate(dx, dy, dz))
|
|
|
|
def scale(self, sx: float, sy: float, sz: float) -> Self:
|
|
"""Scale entity inplace about `dx` in x-axis, `dy` in y-axis and `dz`
|
|
in z-axis, returns `self` (floating interface).
|
|
"""
|
|
return self.transform(Matrix44.scale(sx, sy, sz))
|
|
|
|
def scale_uniform(self, s: float) -> Self:
|
|
"""Scale entity inplace uniform about `s` in x-axis, y-axis and z-axis,
|
|
returns `self` (floating interface).
|
|
"""
|
|
return self.transform(Matrix44.scale(s))
|
|
|
|
def rotate_axis(self, axis: UVec, angle: float) -> Self:
|
|
"""Rotate entity inplace about vector `axis`, returns `self`
|
|
(floating interface).
|
|
|
|
Args:
|
|
axis: rotation axis as tuple or :class:`Vec3`
|
|
angle: rotation angle in radians
|
|
"""
|
|
return self.transform(Matrix44.axis_rotate(axis, angle))
|
|
|
|
def rotate_x(self, angle: float) -> Self:
|
|
"""Rotate entity inplace about x-axis, returns `self`
|
|
(floating interface).
|
|
|
|
Args:
|
|
angle: rotation angle in radians
|
|
"""
|
|
return self.transform(Matrix44.x_rotate(angle))
|
|
|
|
def rotate_y(self, angle: float) -> Self:
|
|
"""Rotate entity inplace about y-axis, returns `self`
|
|
(floating interface).
|
|
|
|
Args:
|
|
angle: rotation angle in radians
|
|
"""
|
|
return self.transform(Matrix44.y_rotate(angle))
|
|
|
|
def rotate_z(self, angle: float) -> Self:
|
|
"""Rotate entity inplace about z-axis, returns `self`
|
|
(floating interface).
|
|
|
|
Args:
|
|
angle: rotation angle in radians
|
|
"""
|
|
return self.transform(Matrix44.z_rotate(angle))
|
|
|
|
def has_hyperlink(self) -> bool:
|
|
"""Returns ``True`` if entity has an attached hyperlink."""
|
|
return bool(self.xdata) and ("PE_URL" in self.xdata) # type: ignore
|
|
|
|
def set_hyperlink(
|
|
self,
|
|
link: str,
|
|
description: Optional[str] = None,
|
|
location: Optional[str] = None,
|
|
):
|
|
"""Set hyperlink of an entity."""
|
|
xdata = [(1001, "PE_URL"), (1000, str(link))]
|
|
if description:
|
|
xdata.append((1002, "{"))
|
|
xdata.append((1000, str(description)))
|
|
if location:
|
|
xdata.append((1000, str(location)))
|
|
xdata.append((1002, "}"))
|
|
|
|
self.discard_xdata("PE_URL")
|
|
self.set_xdata("PE_URL", xdata)
|
|
if self.doc and "PE_URL" not in self.doc.appids:
|
|
self.doc.appids.new("PE_URL")
|
|
return self
|
|
|
|
def get_hyperlink(self) -> tuple[str, str, str]:
|
|
"""Returns hyperlink, description and location."""
|
|
link = ""
|
|
description = ""
|
|
location = ""
|
|
if self.xdata and "PE_URL" in self.xdata:
|
|
xdata = [tag.value for tag in self.get_xdata("PE_URL") if tag.code == 1000]
|
|
if len(xdata):
|
|
link = xdata[0]
|
|
if len(xdata) > 1:
|
|
description = xdata[1]
|
|
if len(xdata) > 2:
|
|
location = xdata[2]
|
|
return link, description, location
|
|
|
|
def remove_dependencies(self, other: Optional[Drawing] = None) -> None:
|
|
"""Remove all dependencies from current document.
|
|
|
|
(internal API)
|
|
"""
|
|
if not self.is_alive:
|
|
return
|
|
|
|
super().remove_dependencies(other)
|
|
# The layer attribute is preserved because layer doesn't need a layer
|
|
# table entry, the layer attributes are reset to default attributes
|
|
# like color is 7 and linetype is CONTINUOUS
|
|
has_linetype = other is not None and (self.dxf.linetype in other.linetypes)
|
|
if not has_linetype:
|
|
self.dxf.linetype = "BYLAYER"
|
|
self.dxf.discard("material_handle")
|
|
self.dxf.discard("visualstyle_handle")
|
|
self.dxf.discard("plotstyle_enum")
|
|
self.dxf.discard("plotstyle_handle")
|
|
|
|
def _new_compound_entity(self, type_: str, dxfattribs) -> Self:
|
|
"""Create and bind new entity with same layout settings as `self`.
|
|
|
|
Used by INSERT & POLYLINE to create appended DXF entities, don't use it
|
|
to create new standalone entities.
|
|
|
|
(internal API)
|
|
"""
|
|
dxfattribs = dxfattribs or {}
|
|
|
|
# if layer is not deliberately set, set same layer as creator entity,
|
|
# at least VERTEX should have the same layer as the POLYGON entity.
|
|
# Don't know if that is also important for the ATTRIB & INSERT entity.
|
|
if "layer" not in dxfattribs:
|
|
dxfattribs["layer"] = self.dxf.layer
|
|
if self.doc:
|
|
entity = factory.create_db_entry(type_, dxfattribs, self.doc)
|
|
else:
|
|
entity = factory.new(type_, dxfattribs)
|
|
entity.dxf.owner = self.dxf.owner
|
|
entity.dxf.paperspace = self.dxf.paperspace
|
|
return entity # type: ignore
|
|
|
|
def register_resources(self, registry: xref.Registry) -> None:
|
|
"""Register required resources to the resource registry."""
|
|
super().register_resources(registry)
|
|
dxf = self.dxf
|
|
registry.add_layer(dxf.layer)
|
|
registry.add_linetype(dxf.linetype)
|
|
registry.add_handle(dxf.get("material_handle"))
|
|
# unsupported resource attributes:
|
|
# - visualstyle_handle
|
|
# - plotstyle_handle
|
|
|
|
def map_resources(self, clone: Self, mapping: xref.ResourceMapper) -> None:
|
|
"""Translate resources from self to the copied entity."""
|
|
super().map_resources(clone, mapping)
|
|
clone.dxf.layer = mapping.get_layer(self.dxf.layer)
|
|
attrib_exist = self.dxf.hasattr
|
|
if attrib_exist("linetype"):
|
|
clone.dxf.linetype = mapping.get_linetype(self.dxf.linetype)
|
|
if attrib_exist("material_handle"):
|
|
clone.dxf.material_handle = mapping.get_handle(self.dxf.material_handle)
|
|
|
|
# unsupported attributes:
|
|
clone.dxf.discard("visualstyle_handle")
|
|
clone.dxf.discard("plotstyle_handle")
|
|
|
|
|
|
@factory.register_entity
|
|
class SeqEnd(DXFGraphic):
|
|
DXFTYPE = "SEQEND"
|
|
|
|
def load_dxf_attribs(
|
|
self, processor: Optional[SubclassProcessor] = None
|
|
) -> DXFNamespace:
|
|
"""Loading interface. (internal API)"""
|
|
# bypass DXFGraphic, loading proxy graphic is skipped!
|
|
dxf = super(DXFGraphic, self).load_dxf_attribs(processor)
|
|
if processor:
|
|
processor.simple_dxfattribs_loader(dxf, acdb_entity_group_codes) # type: ignore
|
|
return dxf
|
|
|
|
|
|
def add_entity(entity: DXFGraphic, layout: BaseLayout) -> None:
|
|
"""Add `entity` entity to the entity database and to the given `layout`."""
|
|
assert entity.dxf.handle is None
|
|
assert layout is not None
|
|
if layout.doc:
|
|
factory.bind(entity, layout.doc)
|
|
layout.add_entity(entity)
|
|
|
|
|
|
def replace_entity(source: DXFGraphic, target: DXFGraphic, layout: BaseLayout) -> None:
|
|
"""Add `target` entity to the entity database and to the given `layout`
|
|
and replace the `source` entity by the `target` entity.
|
|
"""
|
|
assert target.dxf.handle is None
|
|
assert layout is not None
|
|
target.dxf.handle = source.dxf.handle
|
|
if source in layout:
|
|
layout.delete_entity(source)
|
|
if layout.doc:
|
|
factory.bind(target, layout.doc)
|
|
layout.add_entity(target)
|
|
else:
|
|
source.destroy()
|
|
|
|
|
|
def is_graphic_entity(entity: DXFEntity) -> TypeGuard[DXFGraphic]:
|
|
"""Returns ``True`` if the `entity` has a graphical representations and
|
|
can reside in the model space, a paper space or a block layout,
|
|
otherwise the entity is a table or class entry or a DXF object from the
|
|
OBJECTS section.
|
|
"""
|
|
if isinstance(entity, DXFGraphic):
|
|
return True
|
|
if isinstance(entity, DXFTagStorage) and entity.is_graphic_entity:
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_font_name(entity: DXFEntity) -> str:
|
|
"""Returns the font name of any DXF entity.
|
|
|
|
This function always returns a font name even if the entity doesn't support text
|
|
styles. The default font name is "txt".
|
|
"""
|
|
font_name = const.DEFAULT_TEXT_FONT
|
|
doc = entity.doc
|
|
if doc is None:
|
|
return font_name
|
|
try:
|
|
style_name = entity.dxf.get("style", const.DEFAULT_TEXT_STYLE)
|
|
except const.DXFAttributeError:
|
|
return font_name
|
|
try:
|
|
style = doc.styles.get(style_name)
|
|
return style.dxf.font
|
|
except const.DXFTableEntryError:
|
|
return font_name
|