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

478 lines
21 KiB
Python

# Copyright (c) 2019-2024 Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import TYPE_CHECKING, Iterable, Optional, Iterator
from typing_extensions import Self
import copy
from ezdxf.math import Vec3, Matrix44
from ezdxf.lldxf.tags import Tags, group_tags
from ezdxf.lldxf.attributes import (
DXFAttr,
DXFAttributes,
DefSubclass,
XType,
group_code_mapping,
)
from ezdxf.lldxf import const
from ezdxf.entities import factory
from .dxfentity import base_class, SubclassProcessor, DXFEntity, DXFTagStorage
from .dxfgfx import DXFGraphic, acdb_entity
from .dxfobj import DXFObject
from .objectcollection import ObjectCollection
from .copy import default_copy
if TYPE_CHECKING:
from ezdxf.entities import DXFNamespace
from ezdxf.lldxf.tagwriter import AbstractTagWriter
from ezdxf.document import Drawing
__all__ = [
"AcadTable",
"AcadTableBlockContent",
"acad_table_to_block",
"read_acad_table_content",
]
acdb_block_reference = DefSubclass(
"AcDbBlockReference",
{
# Block name: an anonymous block begins with a *T value
"geometry": DXFAttr(2),
# Insertion point:
"insert": DXFAttr(10, xtype=XType.point3d, default=Vec3(0, 0, 0)),
},
)
acdb_block_reference_group_codes = group_code_mapping(acdb_block_reference)
acdb_table = DefSubclass(
"AcDbTable",
{
# Table data version number: 0 = 2010
"version": DXFAttr(280),
# Hard of the TABLESTYLE object:
"table_style_id": DXFAttr(342),
# Handle of the associated anonymous BLOCK containing the graphical
# representation:
"block_record_handle": DXFAttr(343),
# Horizontal direction vector:
"horizontal_direction": DXFAttr(11),
# Flag for table value (unsigned integer):
"table_value": DXFAttr(90),
# Number of rows:
"n_rows": DXFAttr(91),
# Number of columns:
"n_cols": DXFAttr(92),
# Flag for an override:
"override_flag": DXFAttr(93),
# Flag for an override of border color:
"border_color_override_flag": DXFAttr(94),
# Flag for an override of border lineweight:
"border_lineweight_override_flag": DXFAttr(95),
# Flag for an override of border visibility:
"border_visibility_override_flag": DXFAttr(96),
# 141: Row height; this value is repeated, 1 value per row
# 142: Column height; this value is repeated, 1 value per column
# for every cell:
# 171: Cell type; this value is repeated, 1 value per cell:
# 1 = text type
# 2 = block type
# 172: Cell flag value; this value is repeated, 1 value per cell
# 173: Cell merged value; this value is repeated, 1 value per cell
# 174: Boolean flag indicating if the autofit option is set for the
# cell; this value is repeated, 1 value per cell
# 175: Cell border width (applicable only for merged cells); this
# value is repeated, 1 value per cell
# 176: Cell border height (applicable for merged cells); this value
# is repeated, 1 value per cell
# 91: Cell override flag; this value is repeated, 1 value per cell
# (from AutoCAD 2007)
# 178: Flag value for a virtual edge
# 145: Rotation value (real; applicable for a block-type cell and
# a text-type cell)
# 344: Hard pointer ID of the FIELD object. This applies only to a
# text-type cell. If the text in the cell contains one or more
# fields, only the ID of the FIELD object is saved.
# The text string (group codes 1 and 3) is ignored
# 1: Text string in a cell. If the string is shorter than 250
# characters, all characters appear in code 1.
# If the string is longer than 250 characters, it is divided
# into chunks of 250 characters.
# The chunks are contained in one or more code 2 codes.
# If code 2 codes are used, the last group is a code 1 and is
# shorter than 250 characters.
# This value applies only to text-type cells and is repeated,
# 1 value per cell
# 2: Text string in a cell, in 250-character chunks; optional.
# This value applies only to text-type cells and is repeated,
# 1 value per cell
# 340: Hard-pointer ID of the block table record.
# This value applies only to block-type cells and is repeated,
# 1 value per cell
# 144: Block scale (real). This value applies only to block-type
# cells and is repeated, 1 value per cell
# 176: Number of attribute definitions in the block table record
# (applicable only to a block-type cell)
# for every ATTDEF:
# 331: Soft pointer ID of the attribute definition in the
# block table record, referenced by group code 179
# (applicable only for a block-type cell). This value is
# repeated once per attribute definition
# 300: Text string value for an attribute definition, repeated
# once per attribute definition and applicable only for
# a block-type cell
# 7: Text style name (string); override applied at the cell level
# 140: Text height value; override applied at the cell level
# 170: Cell alignment value; override applied at the cell level
# 64: Value for the color of cell content; override applied at the
# cell level
# 63: Value for the background (fill) color of cell content;
# override applied at the cell level
# 69: True color value for the top border of the cell;
# override applied at the cell level
# 65: True color value for the right border of the cell;
# override applied at the cell level
# 66: True color value for the bottom border of the cell;
# override applied at the cell level
# 68: True color value for the left border of the cell;
# override applied at the cell level
# 279: Lineweight for the top border of the cell;
# override applied at the cell level
# 275: Lineweight for the right border of the cell;
# override applied at the cell level
# 276: Lineweight for the bottom border of the cell;
# override applied at the cell level
# 278: Lineweight for the left border of the cell;
# override applied at the cell level
# 283: Boolean flag for whether the fill color is on;
# override applied at the cell level
# 289: Boolean flag for the visibility of the top border of the cell;
# override applied at the cell level
# 285: Boolean flag for the visibility of the right border of the cell;
# override applied at the cell level
# 286: Boolean flag for the visibility of the bottom border of the cell;
# override applied at the cell level
# 288: Boolean flag for the visibility of the left border of the cell;
# override applied at the cell level
# 70: Flow direction;
# override applied at the table entity level
# 40: Horizontal cell margin;
# override applied at the table entity level
# 41: Vertical cell margin;
# override applied at the table entity level
# 280: Flag for whether the title is suppressed;
# override applied at the table entity level
# 281: Flag for whether the header row is suppressed;
# override applied at the table entity level
# 7: Text style name (string);
# override applied at the table entity level.
# There may be one entry for each cell type
# 140: Text height (real);
# override applied at the table entity level.
# There may be one entry for each cell type
# 170: Cell alignment (integer);
# override applied at the table entity level.
# There may be one entry for each cell type
# 63: Color value for cell background or for the vertical, left
# border of the table; override applied at the table entity
# level. There may be one entry for each cell type
# 64: Color value for cell content or for the horizontal, top
# border of the table; override applied at the table entity
# level. There may be one entry for each cell type
# 65: Color value for the horizontal, inside border lines;
# override applied at the table entity level
# 66: Color value for the horizontal, bottom border lines;
# override applied at the table entity level
# 68: Color value for the vertical, inside border lines;
# override applied at the table entity level
# 69: Color value for the vertical, right border lines;
# override applied at the table entity level
# 283: Flag for whether background color is enabled (default = 0);
# override applied at the table entity level.
# There may be one entry for each cell type: 0/1 = Disabled/Enabled
# 274-279: Lineweight for each border type of the cell (default = kLnWtByBlock);
# override applied at the table entity level.
# There may be one group for each cell type
# 284-289: Flag for visibility of each border type of the cell (default = 1);
# override applied at the table entity level.
# There may be one group for each cell type: 0/1 = Invisible/Visible
# 97: Standard/title/header row data type
# 98: Standard/title/header row unit type
# 4: Standard/title/header row format string
#
# AutoCAD 2007 and before:
# 177: Cell override flag value (before AutoCAD 2007)
# 92: Extended cell flags (from AutoCAD 2007), COLLISION: group code
# also used by n_cols
# 301: Text string in a cell. If the string is shorter than 250
# characters, all characters appear in code 302.
# If the string is longer than 250 characters, it is divided into
# chunks of 250 characters.
# The chunks are contained in one or more code 303 codes.
# If code 393 codes are used, the last group is a code 1 and is
# shorter than 250 characters.
# --- WRONG: The text is divided into chunks of group code 2 and the last
# chuck has group code 1.
# This value applies only to text-type cells and is repeated,
# 1 value per cell (from AutoCAD 2007)
# 302: Text string in a cell, in 250-character chunks; optional.
# This value applies only to text-type cells and is repeated,
# 302 value per cell (from AutoCAD 2007)
# --- WRONG: 302 contains all the text as a long string, tested with more
# than 66000 characters
# BricsCAD writes long text in cells with both methods: 302 & (2, 2, 2, ..., 1)
#
# REMARK from Autodesk:
# Group code 178 is a flag value for a virtual edge. A virtual edge is
# used when a grid line is shared by two cells.
# For example, if a table contains one row and two columns and it
# contains cell A and cell B, the central grid line
# contains the right edge of cell A and the left edge of cell B.
# One edge is real, and the other edge is virtual.
# The virtual edge points to the real edge; both edges have the same
# set of properties, including color, lineweight, and visibility.
},
)
acdb_table_group_codes = group_code_mapping(acdb_table)
# todo: implement ACAD_TABLE
class AcadTable(DXFGraphic):
"""DXF ACAD_TABLE entity"""
DXFTYPE = "ACAD_TABLE"
DXFATTRIBS = DXFAttributes(
base_class, acdb_entity, acdb_block_reference, acdb_table
)
MIN_DXF_VERSION_FOR_EXPORT = const.DXF2007
def __init__(self):
super().__init__()
self.data = None
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
"""Copy data."""
assert isinstance(entity, AcadTable)
entity.data = copy.deepcopy(self.data)
def load_dxf_attribs(
self, processor: Optional[SubclassProcessor] = None
) -> DXFNamespace:
dxf = super().load_dxf_attribs(processor)
if processor:
processor.fast_load_dxfattribs(
dxf, acdb_block_reference_group_codes, subclass=2
)
tags = processor.fast_load_dxfattribs(
dxf, acdb_table_group_codes, subclass=3, log=False
)
self.load_table(tags)
return dxf
def load_table(self, tags: Tags):
pass
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
"""Export entity specific data as DXF tags."""
super().export_entity(tagwriter)
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_block_reference.name)
self.dxf.export_dxf_attribs(tagwriter, ["geometry", "insert"])
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_table.name)
self.export_table(tagwriter)
def export_table(self, tagwriter: AbstractTagWriter):
pass
def __referenced_blocks__(self) -> Iterable[str]:
"""Support for "ReferencedBlocks" protocol."""
if self.doc:
block_record_handle = self.dxf.get("block_record_handle", None)
if block_record_handle:
return (block_record_handle,)
return tuple()
acdb_table_style = DefSubclass(
"AcDbTableStyle",
{
# Table style version: 0 = 2010
"version": DXFAttr(280),
# Table style description (string; 255 characters maximum):
"name": DXFAttr(3),
# FlowDirection (integer):
# 0 = Down
# 1 = Up
"flow_direction": DXFAttr(7),
# Flags (bit-coded)
"flags": DXFAttr(7),
# Horizontal cell margin (real; default = 0.06)
"horizontal_cell_margin": DXFAttr(40),
# Vertical cell margin (real; default = 0.06)
"vertical_cell_margin": DXFAttr(41),
# Flag for whether the title is suppressed:
# 0/1 = not suppressed/suppressed
"suppress_title": DXFAttr(280),
# Flag for whether the column heading is suppressed:
# 0/1 = not suppressed/suppressed
"suppress_column_header": DXFAttr(281),
# The following group codes are repeated for every cell in the table
# 7: Text style name (string; default = STANDARD)
# 140: Text height (real)
# 170: Cell alignment (integer)
# 62: Text color (integer; default = BYBLOCK)
# 63: Cell fill color (integer; default = 7)
# 283: Flag for whether background color is enabled (default = 0):
# 0/1 = disabled/enabled
# 90: Cell data type
# 91: Cell unit type
# 274-279: Lineweight associated with each border type of the cell
# (default = kLnWtByBlock)
# 284-289: Flag for visibility associated with each border type of the cell
# (default = 1): 0/1 = Invisible/Visible
# 64-69: Color value associated with each border type of the cell
# (default = BYBLOCK)
},
)
# todo: implement TABLESTYLE
class TableStyle(DXFObject):
"""DXF TABLESTYLE entity
Every ACAD_TABLE has its own table style.
Requires DXF version AC1021/R2007
"""
DXFTYPE = "TABLESTYLE"
DXFATTRIBS = DXFAttributes(base_class, acdb_table_style)
MIN_DXF_VERSION_FOR_EXPORT = const.DXF2007
class TableStyleManager(ObjectCollection[TableStyle]):
def __init__(self, doc: Drawing):
super().__init__(doc, dict_name="ACAD_TABLESTYLE", object_type="TABLESTYLE")
@factory.register_entity
class AcadTableBlockContent(DXFTagStorage):
DXFTYPE = "ACAD_TABLE"
DXFATTRIBS = DXFAttributes(
base_class, acdb_entity, acdb_block_reference, acdb_table
)
def load_dxf_attribs(
self, processor: Optional[SubclassProcessor] = None
) -> DXFNamespace:
dxf = super().load_dxf_attribs(processor)
if processor:
processor.fast_load_dxfattribs(
dxf, acdb_block_reference_group_codes, subclass=2
)
processor.fast_load_dxfattribs(
dxf, acdb_table_group_codes, subclass=3, log=False
)
return dxf
def proxy_graphic_content(self) -> Iterable[DXFGraphic]:
return super().__virtual_entities__()
def _block_content(self) -> Iterator[DXFGraphic]:
block_name: str = self.get_block_name()
return self.doc.blocks.get(block_name, []) # type: ignore
def get_block_name(self) -> str:
return self.dxf.get("geometry", "")
def get_insert_location(self) -> Vec3:
return self.dxf.get("insert", Vec3())
def __virtual_entities__(self) -> Iterator[DXFGraphic]:
"""Implements the SupportsVirtualEntities protocol."""
insert: Vec3 = Vec3(self.get_insert_location())
m: Optional[Matrix44] = None
if insert:
# TODO: OCS transformation (extrusion) is ignored yet
m = Matrix44.translate(insert.x, insert.y, insert.z)
for entity in self._block_content():
try:
clone = entity.copy()
except const.DXFTypeError:
continue
if m is not None:
try:
clone.transform(m)
except: # skip entity at any transformation issue
continue
yield clone
def acad_table_to_block(table: DXFEntity) -> None:
"""Converts the given ACAD_TABLE entity to a block references (INSERT entity).
The original ACAD_TABLE entity will be destroyed.
.. versionadded:: 1.1
"""
if not isinstance(table, AcadTableBlockContent):
return
doc = table.doc
owner = table.dxf.owner
block_name = table.get_block_name()
if doc is None or block_name == "" or owner is None:
return
try:
layout = doc.layouts.get_layout_by_key(owner)
except const.DXFKeyError:
return
# replace ACAD_TABLE entity by INSERT entity
layout.add_blockref(
block_name,
insert=table.get_insert_location(),
dxfattribs={"layer": table.dxf.get("layer", "0")},
)
layout.delete_entity(table) # type: ignore
def read_acad_table_content(table: DXFTagStorage) -> list[list[str]]:
"""Returns the content of an ACAD_TABLE entity as list of table rows.
If the count of table rows or table columns is missing the complete content is
stored in the first row.
"""
if table.dxftype() != "ACAD_TABLE":
raise const.DXFTypeError(f"Expected ACAD_TABLE entity, got {str(table)}")
acdb_table = table.xtags.get_subclass("AcDbTable")
nrows = acdb_table.get_first_value(91, 0)
ncols = acdb_table.get_first_value(92, 0)
split_code = 171 # DXF R2004
if acdb_table.has_tag(302):
split_code = 301 # DXF R2007 and later
values = _load_table_values(acdb_table, split_code)
if nrows * ncols == 0:
return [values]
content: list[list[str]] = []
for index in range(nrows):
start = index * ncols
content.append(values[start : start + ncols])
return content
def _load_table_values(tags: Tags, split_code: int) -> list[str]:
values: list[str] = []
for group in group_tags(tags, splitcode=split_code):
g_tags = Tags(group)
if g_tags.has_tag(302): # DXF R2007 and later
# contains all text as one long string, with more than 66000 chars tested
values.append(g_tags.get_first_value(302))
else:
# DXF R2004
# Text is divided into chunks (2, 2, 2, ..., 1) or (3, 3, 3, ..., 1)
# DXF reference says group code 2, BricsCAD writes group code 3
s = "".join(tag.value for tag in g_tags if 1 <= tag.code <= 3)
values.append(s)
return values