refactor: excel parse
This commit is contained in:
@@ -0,0 +1,636 @@
|
||||
# Copyright (c) 2023, Manfred Moitzi
|
||||
# License: MIT License
|
||||
"""
|
||||
Module to export any DXF document as DXF version R12 without modifying the source
|
||||
document.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
To get the best result use the ODA File Converter add-on::
|
||||
|
||||
from ezdxf.addons import odafc
|
||||
|
||||
odafc.convert("any.dxf", "r12.dxf", version="R12")
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, TextIO, Callable, Optional
|
||||
import os
|
||||
from io import StringIO
|
||||
import logging
|
||||
|
||||
import ezdxf
|
||||
from ezdxf import const, proxygraphic, path
|
||||
from ezdxf.document import Drawing
|
||||
from ezdxf.entities import (
|
||||
BlockRecord,
|
||||
DXFEntity,
|
||||
DXFTagStorage,
|
||||
Ellipse,
|
||||
Hatch,
|
||||
Insert,
|
||||
LWPolyline,
|
||||
MPolygon,
|
||||
MText,
|
||||
Mesh,
|
||||
Polyface,
|
||||
Polyline,
|
||||
Spline,
|
||||
Textstyle,
|
||||
)
|
||||
from ezdxf.entities.polygon import DXFPolygon
|
||||
from ezdxf.addons import MTextExplode
|
||||
from ezdxf.entitydb import EntitySpace
|
||||
from ezdxf.layouts import BlockLayout, VirtualLayout
|
||||
from ezdxf.lldxf.tagwriter import TagWriter, AbstractTagWriter
|
||||
from ezdxf.lldxf.types import DXFTag, TAG_STRING_FORMAT
|
||||
from ezdxf.math import Z_AXIS, Vec3, NULLVEC
|
||||
from ezdxf.r12strict import R12NameTranslator
|
||||
from ezdxf.render import MeshBuilder
|
||||
from ezdxf.sections.table import TextstyleTable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.eztypes import GenericLayoutType
|
||||
|
||||
__all__ = ["R12Exporter", "convert", "saveas", "write"]
|
||||
|
||||
MAX_SAGITTA = 0.01
|
||||
logger = logging.getLogger("ezdxf")
|
||||
|
||||
|
||||
def convert(doc: Drawing, *, max_sagitta: float = MAX_SAGITTA) -> Drawing:
|
||||
"""Export and reload DXF document as DXF version R12.
|
||||
|
||||
Writes the DXF document into a temporary file at the file-system and reloads this
|
||||
file by the :func:`ezdxf.readfile` function.
|
||||
"""
|
||||
stream = StringIO()
|
||||
exporter = R12Exporter(doc, max_sagitta=max_sagitta)
|
||||
exporter.write(stream)
|
||||
stream.seek(0)
|
||||
return ezdxf.read(stream)
|
||||
|
||||
|
||||
def write(doc: Drawing, stream: TextIO, *, max_sagitta: float = MAX_SAGITTA) -> None:
|
||||
"""Write a DXF document as DXF version R12 to a text stream. The `max_sagitta`
|
||||
argument determines the accuracy of the curve flatting for SPLINE and ELLIPSE
|
||||
entities.
|
||||
|
||||
Args:
|
||||
doc: DXF document to export
|
||||
stream: output stream, use :attr:`doc.encoding` as encoding
|
||||
max_sagitta: maximum distance from the center of the curve to the
|
||||
center of the line segment between two approximation points to
|
||||
determine if a segment should be subdivided.
|
||||
|
||||
"""
|
||||
exporter = R12Exporter(doc, max_sagitta=max_sagitta)
|
||||
exporter.write(stream)
|
||||
|
||||
|
||||
def saveas(
|
||||
doc: Drawing, filepath: str | os.PathLike, *, max_sagitta: float = MAX_SAGITTA
|
||||
) -> None:
|
||||
"""Write a DXF document as DXF version R12 to a file. The `max_sagitta`
|
||||
argument determines the accuracy of the curve flatting for SPLINE and ELLIPSE
|
||||
entities.
|
||||
|
||||
Args:
|
||||
doc: DXF document to export
|
||||
filepath: output filename
|
||||
max_sagitta: maximum distance from the center of the curve to the
|
||||
center of the line segment between two approximation points to
|
||||
determine if a segment should be subdivided.
|
||||
|
||||
"""
|
||||
with open(filepath, "wt", encoding=doc.encoding, errors="dxfreplace") as stream:
|
||||
write(
|
||||
doc,
|
||||
stream,
|
||||
max_sagitta=max_sagitta,
|
||||
)
|
||||
|
||||
|
||||
def spline_to_polyline(
|
||||
spline: Spline, max_sagitta: float, min_segments: int
|
||||
) -> Polyline:
|
||||
polyline = Polyline.new(
|
||||
dxfattribs={
|
||||
"layer": spline.dxf.layer,
|
||||
"linetype": spline.dxf.linetype,
|
||||
"color": spline.dxf.color,
|
||||
"flags": const.POLYLINE_3D_POLYLINE,
|
||||
}
|
||||
)
|
||||
|
||||
polyline.append_vertices(points=spline.flattening(max_sagitta, min_segments))
|
||||
polyline.new_seqend()
|
||||
return polyline
|
||||
|
||||
|
||||
def ellipse_to_polyline(
|
||||
ellipse: Ellipse, max_sagitta: float, min_segments: int
|
||||
) -> Polyline:
|
||||
polyline = Polyline.new(
|
||||
dxfattribs={
|
||||
"layer": ellipse.dxf.layer,
|
||||
"linetype": ellipse.dxf.linetype,
|
||||
"color": ellipse.dxf.color,
|
||||
"flags": const.POLYLINE_3D_POLYLINE,
|
||||
}
|
||||
)
|
||||
polyline.append_vertices(points=ellipse.flattening(max_sagitta, min_segments))
|
||||
polyline.new_seqend()
|
||||
return polyline
|
||||
|
||||
|
||||
def lwpolyline_to_polyline(lwpolyline: LWPolyline) -> Polyline:
|
||||
polyline = Polyline.new(
|
||||
dxfattribs={
|
||||
"layer": lwpolyline.dxf.layer,
|
||||
"linetype": lwpolyline.dxf.linetype,
|
||||
"color": lwpolyline.dxf.color,
|
||||
}
|
||||
)
|
||||
polyline.new_seqend()
|
||||
polyline.append_formatted_vertices(lwpolyline.get_points(), format="xyseb")
|
||||
if lwpolyline.is_closed:
|
||||
polyline.close()
|
||||
if lwpolyline.dxf.hasattr("const_width"):
|
||||
width = lwpolyline.dxf.const_width
|
||||
polyline.dxf.default_start_width = width
|
||||
polyline.dxf.default_end_width = width
|
||||
extrusion = Vec3(lwpolyline.dxf.extrusion)
|
||||
if not extrusion.isclose(Z_AXIS):
|
||||
polyline.dxf.extrusion = extrusion
|
||||
elevation = lwpolyline.dxf.elevation
|
||||
polyline.dxf.elevation = Vec3(0, 0, elevation)
|
||||
# Set z-axis of VERTEX.location to elevation?
|
||||
|
||||
return polyline
|
||||
|
||||
|
||||
def mesh_to_polyface_mesh(mesh: Mesh) -> Polyface:
|
||||
builder = MeshBuilder.from_mesh(mesh)
|
||||
return builder.render_polyface(
|
||||
VirtualLayout(),
|
||||
dxfattribs={
|
||||
"layer": mesh.dxf.layer,
|
||||
"linetype": mesh.dxf.linetype,
|
||||
"color": mesh.dxf.color,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_xpl_block_name(entity: DXFEntity) -> str:
|
||||
assert entity.dxf.handle is not None
|
||||
return f"EZDXF_XPL_{entity.dxftype()}_{entity.dxf.handle}"
|
||||
|
||||
|
||||
def export_lwpolyline(exporter: R12Exporter, entity: DXFEntity):
|
||||
assert isinstance(entity, LWPolyline)
|
||||
polyline = lwpolyline_to_polyline(entity)
|
||||
if len(polyline.vertices):
|
||||
polyline.export_dxf(exporter.tagwriter())
|
||||
|
||||
|
||||
def export_mesh(exporter: R12Exporter, entity: DXFEntity):
|
||||
assert isinstance(entity, Mesh)
|
||||
polyface_mesh = mesh_to_polyface_mesh(entity)
|
||||
if len(polyface_mesh.vertices):
|
||||
polyface_mesh.export_dxf(exporter.tagwriter())
|
||||
|
||||
|
||||
def export_spline(exporter: R12Exporter, entity: DXFEntity):
|
||||
assert isinstance(entity, Spline)
|
||||
polyline = spline_to_polyline(
|
||||
entity, exporter.max_sagitta, exporter.min_spline_segments
|
||||
)
|
||||
if len(polyline.vertices):
|
||||
polyline.export_dxf(exporter.tagwriter())
|
||||
|
||||
|
||||
def export_ellipse(exporter: R12Exporter, entity: DXFEntity):
|
||||
assert isinstance(entity, Ellipse)
|
||||
polyline = ellipse_to_polyline(
|
||||
entity, exporter.max_sagitta, exporter.min_ellipse_segments
|
||||
)
|
||||
if len(polyline.vertices):
|
||||
polyline.export_dxf(exporter.tagwriter())
|
||||
|
||||
|
||||
def make_insert(name: str, entity: DXFEntity, location=NULLVEC) -> Insert:
|
||||
return Insert.new(
|
||||
dxfattribs={
|
||||
"name": name,
|
||||
"layer": entity.dxf.layer,
|
||||
"linetype": entity.dxf.linetype,
|
||||
"color": entity.dxf.color,
|
||||
"insert": location,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def export_proxy_graphic(exporter: R12Exporter, entity: DXFEntity):
|
||||
assert isinstance(entity.proxy_graphic, bytes)
|
||||
pg = proxygraphic.ProxyGraphic(entity.proxy_graphic)
|
||||
try:
|
||||
entities = EntitySpace(pg.virtual_entities())
|
||||
except proxygraphic.ProxyGraphicError:
|
||||
return
|
||||
exporter.export_entity_space(entities)
|
||||
|
||||
|
||||
def export_mtext(exporter: R12Exporter, entity: DXFEntity):
|
||||
assert isinstance(entity, MText)
|
||||
layout = VirtualLayout()
|
||||
exporter.explode_mtext(entity, layout)
|
||||
exporter.export_entity_space(layout.entity_space)
|
||||
|
||||
|
||||
def export_virtual_entities(exporter: R12Exporter, entity: DXFEntity):
|
||||
layout = VirtualLayout()
|
||||
try:
|
||||
for e in entity.virtual_entities(): # type: ignore
|
||||
layout.add_entity(e)
|
||||
except Exception:
|
||||
return
|
||||
exporter.export_entity_space(layout.entity_space)
|
||||
|
||||
|
||||
def export_pattern_fill(entity: DXFEntity, block: BlockLayout) -> None:
|
||||
assert isinstance(entity, DXFPolygon)
|
||||
dxfattribs = {
|
||||
"layer": entity.dxf.layer,
|
||||
"color": entity.dxf.color,
|
||||
}
|
||||
for start, end in entity.render_pattern_lines():
|
||||
block.add_line(start, end, dxfattribs=dxfattribs)
|
||||
|
||||
|
||||
def export_solid_fill(
|
||||
entity: DXFPolygon,
|
||||
block: BlockLayout,
|
||||
max_sagitta: float,
|
||||
min_segments: int,
|
||||
) -> None:
|
||||
dxfattribs = {
|
||||
"layer": entity.dxf.layer,
|
||||
"color": entity.dxf.color,
|
||||
}
|
||||
|
||||
extrusion = Vec3(entity.dxf.extrusion)
|
||||
if not extrusion.is_null and not extrusion.isclose(Z_AXIS):
|
||||
dxfattribs["extrusion"] = extrusion
|
||||
|
||||
# triangulation in OCS coordinates, including elevation and offset values:
|
||||
for vertices in entity.triangulate(max_sagitta, min_segments):
|
||||
block.add_solid(vertices, dxfattribs=dxfattribs)
|
||||
|
||||
|
||||
def export_hatch(exporter: R12Exporter, entity: DXFEntity) -> None:
|
||||
assert isinstance(entity, Hatch)
|
||||
# export hatch into an anonymous block
|
||||
block = exporter.new_block(entity)
|
||||
insert = make_insert(block.name, entity)
|
||||
insert.export_dxf(exporter.tagwriter())
|
||||
|
||||
if entity.has_pattern_fill:
|
||||
export_pattern_fill(entity, block)
|
||||
else:
|
||||
export_solid_fill(
|
||||
entity, block, exporter.max_sagitta, exporter.min_spline_segments
|
||||
)
|
||||
|
||||
|
||||
def export_mpolygon(exporter: R12Exporter, entity: DXFEntity) -> None:
|
||||
assert isinstance(entity, MPolygon)
|
||||
# export mpolygon into an anonymous block
|
||||
block = exporter.new_block(entity)
|
||||
insert = make_insert(block.name, entity)
|
||||
insert.export_dxf(exporter.tagwriter())
|
||||
|
||||
# elevation is the z-axis of the vertices
|
||||
path.render_polylines2d(
|
||||
block,
|
||||
path.from_hatch_ocs(entity, offset=Vec3(entity.dxf.offset)),
|
||||
distance=exporter.max_sagitta,
|
||||
segments=exporter.min_spline_segments,
|
||||
extrusion=Vec3(entity.dxf.extrusion),
|
||||
dxfattribs={
|
||||
"layer": entity.dxf.layer,
|
||||
"linetype": entity.dxf.linetype,
|
||||
"color": entity.dxf.color,
|
||||
},
|
||||
)
|
||||
if entity.has_pattern_fill:
|
||||
export_pattern_fill(entity, block)
|
||||
else:
|
||||
export_solid_fill(
|
||||
entity, block, exporter.max_sagitta, exporter.min_spline_segments
|
||||
)
|
||||
|
||||
|
||||
def export_acad_table(exporter: R12Exporter, entity: DXFEntity) -> None:
|
||||
from ezdxf.entities.acad_table import AcadTableBlockContent
|
||||
|
||||
assert isinstance(entity, AcadTableBlockContent)
|
||||
table: AcadTableBlockContent = entity
|
||||
location = table.get_insert_location()
|
||||
block_name = table.get_block_name()
|
||||
if not block_name.startswith("*T"):
|
||||
return
|
||||
try:
|
||||
acdb_entity = table.xtags.get_subclass("AcDbEntity")
|
||||
except const.DXFIndexError:
|
||||
return
|
||||
layer = acdb_entity.get_first_value(8, "0")
|
||||
insert = Insert.new(
|
||||
dxfattribs={"name": block_name, "layer": layer, "insert": location}
|
||||
)
|
||||
insert.export_dxf(exporter.tagwriter())
|
||||
|
||||
|
||||
# Planned features: explode complex newer entity types into DXF primitives.
|
||||
# currently skipped entity types:
|
||||
# - ACAD_TABLE: graphic as geometry block is available
|
||||
# --------------------------------------------------------------------------------------
|
||||
# - all ACIS based entities: tessellated meshes could be exported, but very much work
|
||||
# and beyond my current knowledge
|
||||
# - IMAGE and UNDERLAY: no support possible
|
||||
# - XRAY and XLINE: no support possible (infinite lines)
|
||||
|
||||
# Possible name tags to translate:
|
||||
# 1 The primary text value for an entity - never a name
|
||||
# 2 A name: Attribute tag, Block name, and so on. Also used to identify a DXF section or
|
||||
# table name
|
||||
# 3 Other textual or name values - only in DIMENSION a name
|
||||
# 4 Other textual values - never a name!
|
||||
# 5 Entity handle expressed as a hexadecimal string (fixed)
|
||||
# 6 Line type name (fixed)
|
||||
# 7 Text style name (fixed)
|
||||
# 8 Layer name (fixed)
|
||||
# 1001: AppID
|
||||
# 1003: layer name in XDATA (fixed)
|
||||
NAME_TAG_CODES = {2, 3, 6, 7, 8, 1001, 1003}
|
||||
|
||||
|
||||
class R12TagWriter(TagWriter):
|
||||
def __init__(self, stream: TextIO):
|
||||
super().__init__(stream, dxfversion=const.DXF12, write_handles=False)
|
||||
self.skip_xdata = False
|
||||
self.current_entity = ""
|
||||
self.translator = R12NameTranslator()
|
||||
|
||||
def set_stream(self, stream: TextIO) -> None:
|
||||
self._stream = stream
|
||||
|
||||
def write_tag(self, tag: DXFTag) -> None:
|
||||
code, value = tag
|
||||
if code == 0:
|
||||
self.current_entity = str(value)
|
||||
if code > 999 and self.skip_xdata:
|
||||
return
|
||||
if code in NAME_TAG_CODES:
|
||||
self._stream.write(
|
||||
TAG_STRING_FORMAT % (code, self.sanitize_name(code, value))
|
||||
)
|
||||
else:
|
||||
self._stream.write(tag.dxfstr())
|
||||
|
||||
def write_tag2(self, code: int, value) -> None:
|
||||
if code > 999 and self.skip_xdata:
|
||||
return
|
||||
if code == 0:
|
||||
self.current_entity = str(value)
|
||||
if code in NAME_TAG_CODES:
|
||||
value = self.sanitize_name(code, value)
|
||||
self._stream.write(TAG_STRING_FORMAT % (code, value))
|
||||
|
||||
def sanitize_name(self, code: int, name: str) -> str:
|
||||
# sanitize group code 3 + 4
|
||||
# LTYPE - <description> has group code - not a table name
|
||||
# STYLE - <font> has group code (3) - not a table name
|
||||
# STYLE - <bigfont> has group code (4) - not a table name
|
||||
# DIMSTYLE - <dimpost> has group code e.g. "<> mm" (3) - not a table name
|
||||
# DIMSTYLE - <dimapost> has group code (4) - not a table name
|
||||
# ATTDEF - <prompt> has group code (3) - not a table name
|
||||
# DIMENSION - <dimstyle> has group code (3) - is a table name!
|
||||
if code == 3 and self.current_entity != "DIMENSION":
|
||||
return name
|
||||
return self.translator.translate(name)
|
||||
|
||||
|
||||
class SpecialStyleTable:
|
||||
def __init__(self, styles: TextstyleTable, extra_styles: TextstyleTable):
|
||||
self.styles = styles
|
||||
self.extra_styles = extra_styles
|
||||
|
||||
def get_text_styles(self) -> list[Textstyle]:
|
||||
entries = list(self.styles.entries.values())
|
||||
for name, extra_style in self.extra_styles.entries.items():
|
||||
if not self.styles.has_entry(name):
|
||||
entries.append(extra_style)
|
||||
return entries
|
||||
|
||||
def export_dxf(self, tagwriter: AbstractTagWriter) -> None:
|
||||
text_styles = self.get_text_styles()
|
||||
tagwriter.write_tag2(0, "TABLE")
|
||||
tagwriter.write_tag2(2, "STYLE")
|
||||
tagwriter.write_tag2(70, len(text_styles))
|
||||
for style in text_styles:
|
||||
style.export_dxf(tagwriter)
|
||||
tagwriter.write_tag2(0, "ENDTAB")
|
||||
|
||||
|
||||
EOF_STR = "0\nEOF\n"
|
||||
|
||||
|
||||
def detect_max_block_number(names: list[str]) -> int:
|
||||
max_number = 0
|
||||
for name in names:
|
||||
name = name.upper()
|
||||
if not name.startswith("*"):
|
||||
continue
|
||||
try: # *U10
|
||||
number = int(name[2:])
|
||||
except ValueError:
|
||||
continue
|
||||
max_number = max(max_number, number)
|
||||
return max_number + 1
|
||||
|
||||
|
||||
class R12Exporter:
|
||||
def __init__(self, doc: Drawing, max_sagitta: float = 0.01):
|
||||
assert isinstance(doc, Drawing)
|
||||
self._doc = doc
|
||||
self._tagwriter = R12TagWriter(StringIO())
|
||||
self.max_sagitta = float(max_sagitta) # flattening SPLINE, ELLIPSE
|
||||
self.min_spline_segments: int = 4 # flattening SPLINE
|
||||
self.min_ellipse_segments: int = 8 # flattening ELLIPSE
|
||||
self._extra_doc = ezdxf.new("R12")
|
||||
self._next_block_number = detect_max_block_number(
|
||||
[br.dxf.name for br in doc.block_records]
|
||||
)
|
||||
# Exporters are required to convert newer entity types into DXF R12 types.
|
||||
# All newer entity types without an exporter will be ignored.
|
||||
self.exporters: dict[str, Callable[[R12Exporter, DXFEntity], None]] = {
|
||||
"LWPOLYLINE": export_lwpolyline,
|
||||
"MESH": export_mesh,
|
||||
"SPLINE": export_spline,
|
||||
"ELLIPSE": export_ellipse,
|
||||
"MTEXT": export_mtext,
|
||||
"LEADER": export_virtual_entities,
|
||||
"MLEADER": export_virtual_entities,
|
||||
"MULTILEADER": export_virtual_entities,
|
||||
"MLINE": export_virtual_entities,
|
||||
"HATCH": export_hatch,
|
||||
"MPOLYGON": export_mpolygon,
|
||||
"ACAD_TABLE": export_acad_table,
|
||||
}
|
||||
|
||||
def disable_exporter(self, entity_type: str):
|
||||
del self.exporters[entity_type]
|
||||
|
||||
@property
|
||||
def doc(self) -> Drawing:
|
||||
return self._doc
|
||||
|
||||
def tagwriter(self, stream: Optional[TextIO] = None) -> R12TagWriter:
|
||||
if stream is not None:
|
||||
self._tagwriter.set_stream(stream)
|
||||
return self._tagwriter
|
||||
|
||||
def write(self, stream: TextIO) -> None:
|
||||
"""Write DXF document to text stream."""
|
||||
stream.write(self.to_string())
|
||||
|
||||
def to_string(self) -> str:
|
||||
"""Export DXF document as string."""
|
||||
# export layouts before blocks: may create new anonymous blocks
|
||||
entities = self.export_layouts_to_string()
|
||||
# export blocks before HEADER and TABLES sections: may create new text styles
|
||||
blocks = self.export_blocks_to_string()
|
||||
|
||||
return "".join(
|
||||
(
|
||||
self.export_header_to_string(),
|
||||
self.export_tables_to_string(),
|
||||
blocks,
|
||||
entities,
|
||||
EOF_STR,
|
||||
)
|
||||
)
|
||||
|
||||
def next_block_name(self, char: str) -> str:
|
||||
name = f"*{char}{self._next_block_number}"
|
||||
self._next_block_number += 1
|
||||
return name
|
||||
|
||||
def new_block(self, entity: DXFEntity) -> BlockLayout:
|
||||
name = self.next_block_name("U")
|
||||
return self._extra_doc.blocks.new(
|
||||
name,
|
||||
dxfattribs={
|
||||
"layer": entity.dxf.get("layer", "0"),
|
||||
"flags": const.BLK_ANONYMOUS,
|
||||
},
|
||||
)
|
||||
|
||||
def export_header_to_string(self) -> str:
|
||||
in_memory_stream = StringIO()
|
||||
self.doc.header.export_dxf(self.tagwriter(in_memory_stream))
|
||||
return in_memory_stream.getvalue()
|
||||
|
||||
def export_tables_to_string(self) -> str:
|
||||
# DXF R12 does not support XDATA in tables according Autodesk DWG TrueView
|
||||
in_memory_stream = StringIO()
|
||||
tables = self.doc.tables
|
||||
preserve_table = tables.styles
|
||||
tables.styles = SpecialStyleTable(self.doc.styles, self._extra_doc.styles) # type: ignore
|
||||
|
||||
tagwriter = self.tagwriter(in_memory_stream)
|
||||
tagwriter.skip_xdata = True
|
||||
tables.export_dxf(tagwriter)
|
||||
tables.styles = preserve_table
|
||||
tagwriter.skip_xdata = False
|
||||
return in_memory_stream.getvalue()
|
||||
|
||||
def export_blocks_to_string(self) -> str:
|
||||
in_memory_stream = StringIO()
|
||||
self._tagwriter.set_stream(in_memory_stream)
|
||||
|
||||
self._write_section_header("BLOCKS")
|
||||
for block_record in self.doc.block_records:
|
||||
if block_record.is_any_paperspace and not block_record.is_active_paperspace:
|
||||
continue
|
||||
name = block_record.dxf.name.lower()
|
||||
if name in ("$model_space", "$paper_space"):
|
||||
# These block names collide with the translated names of the *Model_Space
|
||||
# and the *Paper_Space blocks.
|
||||
continue
|
||||
self._export_block_record(block_record)
|
||||
|
||||
extra_blocks = self.get_extra_blocks()
|
||||
while len(extra_blocks):
|
||||
for block_record in extra_blocks:
|
||||
self._export_block_record(block_record)
|
||||
self.discard_extra_block(block_record.dxf.name)
|
||||
# block record export can create further blocks
|
||||
extra_blocks = self.get_extra_blocks()
|
||||
|
||||
self._write_endsec()
|
||||
return in_memory_stream.getvalue()
|
||||
|
||||
def discard_extra_block(self, name: str) -> None:
|
||||
self._extra_doc.block_records.discard(name)
|
||||
|
||||
def get_extra_blocks(self) -> list[BlockRecord]:
|
||||
return [
|
||||
br for br in self._extra_doc.block_records if br.dxf.name.startswith("*U")
|
||||
]
|
||||
|
||||
def explode_mtext(self, mtext: MText, layout: GenericLayoutType):
|
||||
with MTextExplode(layout, self._extra_doc) as xpl:
|
||||
xpl.explode(mtext, destroy=False)
|
||||
|
||||
def export_layouts_to_string(self) -> str:
|
||||
in_memory_stream = StringIO()
|
||||
self._tagwriter.set_stream(in_memory_stream)
|
||||
|
||||
self._write_section_header("ENTITIES")
|
||||
self.export_entity_space(self.doc.modelspace().entity_space)
|
||||
self.export_entity_space(self.doc.paperspace().entity_space)
|
||||
self._write_endsec()
|
||||
return in_memory_stream.getvalue()
|
||||
|
||||
def _export_block_record(self, block_record: BlockRecord):
|
||||
tagwriter = self._tagwriter
|
||||
assert block_record.block is not None
|
||||
block_record.block.export_dxf(tagwriter)
|
||||
if not block_record.is_any_layout:
|
||||
self.export_entity_space(block_record.entity_space)
|
||||
assert block_record.endblk is not None
|
||||
block_record.endblk.export_dxf(tagwriter)
|
||||
|
||||
def export_entity_space(self, space: EntitySpace):
|
||||
tagwriter = self._tagwriter
|
||||
for entity in space:
|
||||
if entity.MIN_DXF_VERSION_FOR_EXPORT > const.DXF12 or isinstance(
|
||||
entity, DXFTagStorage
|
||||
):
|
||||
exporter = self.exporters.get(entity.dxftype())
|
||||
if exporter:
|
||||
exporter(self, entity)
|
||||
continue
|
||||
if entity.proxy_graphic:
|
||||
export_proxy_graphic(self, entity)
|
||||
else:
|
||||
entity.export_dxf(tagwriter)
|
||||
|
||||
def _write_section_header(self, name: str) -> None:
|
||||
self._tagwriter.write_str(f" 0\nSECTION\n 2\n{name}\n")
|
||||
|
||||
def _write_endsec(self) -> None:
|
||||
self._tagwriter.write_tag2(0, "ENDSEC")
|
||||
Reference in New Issue
Block a user