637 lines
21 KiB
Python
637 lines
21 KiB
Python
# 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")
|