905 lines
30 KiB
Python
905 lines
30 KiB
Python
# Copyright (c) 2019-2024, Manfred Moitzi
|
|
# License: MIT License
|
|
from __future__ import annotations
|
|
from typing import TYPE_CHECKING, Iterable, Mapping, Optional
|
|
import json
|
|
|
|
from ezdxf.sections.tables import TABLENAMES
|
|
from ezdxf.lldxf.tags import Tags
|
|
from ezdxf.entities import BoundaryPathType, EdgeType
|
|
import numpy as np
|
|
|
|
if TYPE_CHECKING:
|
|
from ezdxf.lldxf.types import DXFTag
|
|
from ezdxf.entities import (
|
|
Insert,
|
|
MText,
|
|
LWPolyline,
|
|
Polyline,
|
|
Spline,
|
|
Leader,
|
|
Dimension,
|
|
Image,
|
|
Mesh,
|
|
Hatch,
|
|
MPolygon,
|
|
Wipeout,
|
|
)
|
|
from ezdxf.entities import DXFEntity, Linetype
|
|
from ezdxf.entities.polygon import DXFPolygon
|
|
from ezdxf.layouts import BlockLayout
|
|
|
|
__all__ = [
|
|
"entities_to_code",
|
|
"block_to_code",
|
|
"table_entries_to_code",
|
|
"black",
|
|
]
|
|
|
|
|
|
def black(code: str, line_length=88, fast: bool = True) -> str:
|
|
"""Returns the source `code` as a single string formatted by `Black`_
|
|
|
|
Requires the installed `Black`_ formatter::
|
|
|
|
pip3 install black
|
|
|
|
Args:
|
|
code: source code
|
|
line_length: max. source code line length
|
|
fast: ``True`` for fast mode, ``False`` to check that the reformatted
|
|
code is valid
|
|
|
|
Raises:
|
|
ImportError: Black is not available
|
|
|
|
.. _black: https://pypi.org/project/black/
|
|
|
|
"""
|
|
|
|
import black
|
|
|
|
mode = black.FileMode()
|
|
mode.line_length = line_length
|
|
return black.format_file_contents(code, fast=fast, mode=mode)
|
|
|
|
|
|
def entities_to_code(
|
|
entities: Iterable[DXFEntity],
|
|
layout: str = "layout",
|
|
ignore: Optional[Iterable[str]] = None,
|
|
) -> Code:
|
|
"""
|
|
Translates DXF entities into Python source code to recreate this entities
|
|
by ezdxf.
|
|
|
|
Args:
|
|
entities: iterable of DXFEntity
|
|
layout: variable name of the layout (model space or block) as string
|
|
ignore: iterable of entities types to ignore as strings
|
|
like ``['IMAGE', 'DIMENSION']``
|
|
|
|
Returns:
|
|
:class:`Code`
|
|
|
|
"""
|
|
code = _SourceCodeGenerator(layout=layout)
|
|
code.translate_entities(entities, ignore=ignore)
|
|
return code.code
|
|
|
|
|
|
def block_to_code(
|
|
block: BlockLayout,
|
|
drawing: str = "doc",
|
|
ignore: Optional[Iterable[str]] = None,
|
|
) -> Code:
|
|
"""
|
|
Translates a BLOCK into Python source code to recreate the BLOCK by ezdxf.
|
|
|
|
Args:
|
|
block: block definition layout
|
|
drawing: variable name of the drawing as string
|
|
ignore: iterable of entities types to ignore as strings
|
|
like ['IMAGE', 'DIMENSION']
|
|
|
|
Returns:
|
|
:class:`Code`
|
|
|
|
"""
|
|
assert block.block is not None
|
|
dxfattribs = _purge_handles(block.block.dxfattribs())
|
|
block_name = dxfattribs.pop("name")
|
|
base_point = dxfattribs.pop("base_point")
|
|
code = _SourceCodeGenerator(layout="b")
|
|
prolog = f'b = {drawing}.blocks.new("{block_name}", base_point={base_point}, dxfattribs={{'
|
|
code.add_source_code_line(prolog)
|
|
code.add_source_code_lines(_fmt_mapping(dxfattribs, indent=4))
|
|
code.add_source_code_line(" }")
|
|
code.add_source_code_line(")")
|
|
code.translate_entities(block, ignore=ignore)
|
|
return code.code
|
|
|
|
|
|
def table_entries_to_code(
|
|
entities: Iterable[DXFEntity], drawing="doc"
|
|
) -> Code:
|
|
code = _SourceCodeGenerator(doc=drawing)
|
|
code.translate_entities(entities)
|
|
return code.code
|
|
|
|
|
|
class Code:
|
|
"""Source code container."""
|
|
|
|
def __init__(self) -> None:
|
|
self.code: list[str] = []
|
|
# global imports -> indentation level 0:
|
|
self.imports: set[str] = set()
|
|
# layer names as string:
|
|
self.layers: set[str] = set()
|
|
# text style name as string, requires a TABLE entry:
|
|
self.styles: set[str] = set()
|
|
# line type names as string, requires a TABLE entry:
|
|
self.linetypes: set[str] = set()
|
|
# dimension style names as string, requires a TABLE entry:
|
|
self.dimstyles: set[str] = set()
|
|
# block names as string, requires a BLOCK definition:
|
|
self.blocks: set[str] = set()
|
|
|
|
def code_str(self, indent: int = 0) -> str:
|
|
"""Returns the source code as a single string.
|
|
|
|
Args:
|
|
indent: source code indentation count by spaces
|
|
|
|
"""
|
|
lead_str = " " * indent
|
|
return "\n".join(lead_str + line for line in self.code)
|
|
|
|
def black_code_str(self, line_length=88) -> str:
|
|
"""Returns the source code as a single string formatted by `Black`_
|
|
|
|
Args:
|
|
line_length: max. source code line length
|
|
|
|
Raises:
|
|
ImportError: Black is not available
|
|
|
|
"""
|
|
return black(self.code_str(), line_length)
|
|
|
|
def __str__(self) -> str:
|
|
"""Returns the source code as a single string."""
|
|
|
|
return self.code_str()
|
|
|
|
def import_str(self, indent: int = 0) -> str:
|
|
"""Returns required imports as a single string.
|
|
|
|
Args:
|
|
indent: source code indentation count by spaces
|
|
|
|
"""
|
|
lead_str = " " * indent
|
|
return "\n".join(lead_str + line for line in self.imports)
|
|
|
|
def add_import(self, statement: str) -> None:
|
|
"""Add import statement, identical import statements are merged
|
|
together.
|
|
"""
|
|
self.imports.add(statement)
|
|
|
|
def add_line(self, code: str, indent: int = 0) -> None:
|
|
"""Add a single source code line without line ending ``\\n``."""
|
|
self.code.append(" " * indent + code)
|
|
|
|
def add_lines(self, code: Iterable[str], indent: int = 0) -> None:
|
|
"""Add multiple source code lines without line ending ``\\n``."""
|
|
for line in code:
|
|
self.add_line(line, indent=indent)
|
|
|
|
def merge(self, code: Code, indent: int = 0) -> None:
|
|
"""Add another :class:`Code` object."""
|
|
# merge used resources
|
|
self.imports.update(code.imports)
|
|
self.layers.update(code.layers)
|
|
self.linetypes.update(code.linetypes)
|
|
self.styles.update(code.styles)
|
|
self.dimstyles.update(code.dimstyles)
|
|
self.blocks.update(code.blocks)
|
|
|
|
# append source code lines
|
|
self.add_lines(code.code, indent=indent)
|
|
|
|
|
|
_PURGE_DXF_ATTRIBUTES = {
|
|
"handle",
|
|
"owner",
|
|
"paperspace",
|
|
"material_handle",
|
|
"visualstyle_handle",
|
|
"plotstyle_handle",
|
|
}
|
|
|
|
|
|
def _purge_handles(attribs: dict) -> dict:
|
|
"""Purge handles from DXF attributes which will be invalid in a new
|
|
document, or which will be set automatically by adding an entity to a
|
|
layout (paperspace).
|
|
|
|
Args:
|
|
attribs: entity DXF attributes dictionary
|
|
|
|
"""
|
|
return {k: v for k, v in attribs.items() if k not in _PURGE_DXF_ATTRIBUTES}
|
|
|
|
|
|
def _fmt_mapping(mapping: Mapping, indent: int = 0) -> Iterable[str]:
|
|
# key is always a string
|
|
fmt = " " * indent + "'{}': {},"
|
|
for k, v in mapping.items():
|
|
assert isinstance(k, str)
|
|
if isinstance(v, str):
|
|
v = json.dumps(v) # for correct escaping of quotes
|
|
else:
|
|
v = str(v) # format uses repr() for Vec3s
|
|
yield fmt.format(k, v)
|
|
|
|
|
|
def _fmt_list(l: Iterable, indent: int = 0) -> Iterable[str]:
|
|
def cleanup(values: Iterable) -> Iterable:
|
|
for value in values:
|
|
if isinstance(value, np.float64):
|
|
yield float(value)
|
|
else:
|
|
yield value
|
|
|
|
fmt = " " * indent + "{},"
|
|
for v in l:
|
|
if not isinstance(v, (float, int, str)):
|
|
v = tuple(cleanup(v))
|
|
yield fmt.format(str(v))
|
|
|
|
|
|
def _fmt_api_call(
|
|
func_call: str, args: Iterable[str], dxfattribs: dict
|
|
) -> list[str]:
|
|
attributes = dict(dxfattribs)
|
|
args = list(args) if args else []
|
|
|
|
def fmt_keywords() -> Iterable[str]:
|
|
for arg in args:
|
|
if arg not in attributes:
|
|
continue
|
|
value = attributes.pop(arg)
|
|
if isinstance(value, str):
|
|
valuestr = json.dumps(value) # quoted string!
|
|
else:
|
|
valuestr = str(value)
|
|
yield " {}={},".format(arg, valuestr)
|
|
|
|
s = [func_call]
|
|
s.extend(fmt_keywords())
|
|
s.append(" dxfattribs={")
|
|
s.extend(_fmt_mapping(attributes, indent=8))
|
|
s.extend(
|
|
[
|
|
" },",
|
|
")",
|
|
]
|
|
)
|
|
return s
|
|
|
|
|
|
def _fmt_dxf_tags(tags: Iterable[DXFTag], indent: int = 0):
|
|
fmt = " " * indent + "dxftag({}, {}),"
|
|
for code, value in tags:
|
|
assert isinstance(code, int)
|
|
if isinstance(value, str):
|
|
value = json.dumps(value) # for correct escaping of quotes
|
|
else:
|
|
value = str(value) # format uses repr() for Vec3s
|
|
yield fmt.format(code, value)
|
|
|
|
|
|
class _SourceCodeGenerator:
|
|
"""
|
|
The :class:`_SourceCodeGenerator` translates DXF entities into Python source
|
|
code for creating the same DXF entity in another model space or block
|
|
definition.
|
|
|
|
:ivar code: list of source code lines without line endings
|
|
:ivar required_imports: list of import source code lines, which are required
|
|
to create executable Python code.
|
|
|
|
"""
|
|
|
|
def __init__(self, layout: str = "layout", doc: str = "doc"):
|
|
self.doc = doc
|
|
self.layout = layout
|
|
self.code = Code()
|
|
|
|
def translate_entity(self, entity: DXFEntity) -> None:
|
|
"""Translates one DXF entity into Python source code. The generated
|
|
source code is appended to the attribute `source_code`.
|
|
|
|
Args:
|
|
entity: DXFEntity object
|
|
|
|
"""
|
|
dxftype = entity.dxftype()
|
|
try:
|
|
entity_translator = getattr(self, "_" + dxftype.lower())
|
|
except AttributeError:
|
|
self.add_source_code_line(f'# unsupported DXF entity "{dxftype}"')
|
|
else:
|
|
entity_translator(entity)
|
|
|
|
def translate_entities(
|
|
self,
|
|
entities: Iterable[DXFEntity],
|
|
ignore: Optional[Iterable[str]] = None,
|
|
) -> None:
|
|
"""Translates multiple DXF entities into Python source code. The
|
|
generated source code is appended to the attribute `source_code`.
|
|
|
|
Args:
|
|
entities: iterable of DXFEntity
|
|
ignore: iterable of entities types to ignore as strings
|
|
like ['IMAGE', 'DIMENSION']
|
|
|
|
"""
|
|
ignore = set(ignore) if ignore else set()
|
|
|
|
for entity in entities:
|
|
if entity.dxftype() not in ignore:
|
|
self.translate_entity(entity)
|
|
|
|
def add_used_resources(self, dxfattribs: Mapping) -> None:
|
|
"""Register used resources like layers, line types, text styles and
|
|
dimension styles.
|
|
|
|
Args:
|
|
dxfattribs: DXF attributes dictionary
|
|
|
|
"""
|
|
if "layer" in dxfattribs:
|
|
self.code.layers.add(dxfattribs["layer"])
|
|
if "linetype" in dxfattribs:
|
|
self.code.linetypes.add(dxfattribs["linetype"])
|
|
if "style" in dxfattribs:
|
|
self.code.styles.add(dxfattribs["style"])
|
|
if "dimstyle" in dxfattribs:
|
|
self.code.dimstyles.add(dxfattribs["dimstyle"])
|
|
|
|
def add_import_statement(self, statement: str) -> None:
|
|
self.code.add_import(statement)
|
|
|
|
def add_source_code_line(self, code: str) -> None:
|
|
self.code.add_line(code)
|
|
|
|
def add_source_code_lines(self, code: Iterable[str]) -> None:
|
|
assert not isinstance(code, str)
|
|
self.code.add_lines(code)
|
|
|
|
def add_list_source_code(
|
|
self,
|
|
values: Iterable,
|
|
prolog: str = "[",
|
|
epilog: str = "]",
|
|
indent: int = 0,
|
|
) -> None:
|
|
fmt_str = " " * indent + "{}"
|
|
self.add_source_code_line(fmt_str.format(prolog))
|
|
self.add_source_code_lines(_fmt_list(values, indent=4 + indent))
|
|
self.add_source_code_line(fmt_str.format(epilog))
|
|
|
|
def add_dict_source_code(
|
|
self,
|
|
mapping: Mapping,
|
|
prolog: str = "{",
|
|
epilog: str = "}",
|
|
indent: int = 0,
|
|
) -> None:
|
|
fmt_str = " " * indent + "{}"
|
|
self.add_source_code_line(fmt_str.format(prolog))
|
|
self.add_source_code_lines(_fmt_mapping(mapping, indent=4 + indent))
|
|
self.add_source_code_line(fmt_str.format(epilog))
|
|
|
|
def add_tags_source_code(
|
|
self, tags: Tags, prolog="tags = Tags(", epilog=")", indent=4
|
|
):
|
|
fmt_str = " " * indent + "{}"
|
|
self.add_source_code_line(fmt_str.format(prolog))
|
|
self.add_source_code_lines(_fmt_dxf_tags(tags, indent=4 + indent))
|
|
self.add_source_code_line(fmt_str.format(epilog))
|
|
|
|
def generic_api_call(
|
|
self, dxftype: str, dxfattribs: dict, prefix: str = "e = "
|
|
) -> Iterable[str]:
|
|
"""Returns the source code strings to create a DXF entity by a generic
|
|
`new_entity()` call.
|
|
|
|
Args:
|
|
dxftype: DXF entity type as string, like 'LINE'
|
|
dxfattribs: DXF attributes dictionary
|
|
prefix: prefix string like variable assignment 'e = '
|
|
|
|
"""
|
|
dxfattribs = _purge_handles(dxfattribs)
|
|
self.add_used_resources(dxfattribs)
|
|
s = [
|
|
f"{prefix}{self.layout}.new_entity(",
|
|
f" '{dxftype}',",
|
|
" dxfattribs={",
|
|
]
|
|
s.extend(_fmt_mapping(dxfattribs, indent=8))
|
|
s.extend(
|
|
[
|
|
" },",
|
|
")",
|
|
]
|
|
)
|
|
return s
|
|
|
|
def api_call(
|
|
self,
|
|
api_call: str,
|
|
args: Iterable[str],
|
|
dxfattribs: dict,
|
|
prefix: str = "e = ",
|
|
) -> Iterable[str]:
|
|
"""Returns the source code strings to create a DXF entity by the
|
|
specialised API call.
|
|
|
|
Args:
|
|
api_call: API function call like 'add_line('
|
|
args: DXF attributes to pass as arguments
|
|
dxfattribs: DXF attributes dictionary
|
|
prefix: prefix string like variable assignment 'e = '
|
|
|
|
"""
|
|
dxfattribs = _purge_handles(dxfattribs)
|
|
func_call = f"{prefix}{self.layout}.{api_call}"
|
|
return _fmt_api_call(func_call, args, dxfattribs)
|
|
|
|
def new_table_entry(self, dxftype: str, dxfattribs: dict) -> Iterable[str]:
|
|
"""Returns the source code strings to create a new table entity by
|
|
ezdxf.
|
|
|
|
Args:
|
|
dxftype: table entry type as string, like 'LAYER'
|
|
dxfattribs: DXF attributes dictionary
|
|
|
|
"""
|
|
table = f"{self.doc}.{TABLENAMES[dxftype]}"
|
|
dxfattribs = _purge_handles(dxfattribs)
|
|
name = dxfattribs.pop("name")
|
|
s = [
|
|
f"if '{name}' not in {table}:",
|
|
f" t = {table}.new(",
|
|
f" '{name}',",
|
|
" dxfattribs={",
|
|
]
|
|
s.extend(_fmt_mapping(dxfattribs, indent=12))
|
|
s.extend(
|
|
[
|
|
" },",
|
|
" )",
|
|
]
|
|
)
|
|
return s
|
|
|
|
# simple graphical types
|
|
|
|
def _line(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.api_call("add_line(", ["start", "end"], entity.dxfattribs())
|
|
)
|
|
|
|
def _point(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.api_call("add_point(", ["location"], entity.dxfattribs())
|
|
)
|
|
|
|
def _circle(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.api_call(
|
|
"add_circle(", ["center", "radius"], entity.dxfattribs()
|
|
)
|
|
)
|
|
|
|
def _arc(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.api_call(
|
|
"add_arc(",
|
|
["center", "radius", "start_angle", "end_angle"],
|
|
entity.dxfattribs(),
|
|
)
|
|
)
|
|
|
|
def _text(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.api_call("add_text(", ["text"], entity.dxfattribs())
|
|
)
|
|
|
|
def _solid(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("SOLID", entity.dxfattribs())
|
|
)
|
|
|
|
def _trace(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("TRACE", entity.dxfattribs())
|
|
)
|
|
|
|
def _3dface(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("3DFACE", entity.dxfattribs())
|
|
)
|
|
|
|
def _shape(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.api_call(
|
|
"add_shape(", ["name", "insert", "size"], entity.dxfattribs()
|
|
)
|
|
)
|
|
|
|
def _attrib(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.api_call(
|
|
"add_attrib(", ["tag", "text", "insert"], entity.dxfattribs()
|
|
)
|
|
)
|
|
|
|
def _attdef(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("ATTDEF", entity.dxfattribs())
|
|
)
|
|
|
|
def _ellipse(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.api_call(
|
|
"add_ellipse(",
|
|
["center", "major_axis", "ratio", "start_param", "end_param"],
|
|
entity.dxfattribs(),
|
|
)
|
|
)
|
|
|
|
def _viewport(self, entity: DXFEntity) -> None:
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("VIEWPORT", entity.dxfattribs())
|
|
)
|
|
self.add_source_code_line(
|
|
'# Set valid handles or remove attributes ending with "_handle", '
|
|
"otherwise the DXF file is invalid for AutoCAD"
|
|
)
|
|
|
|
# complex graphical types
|
|
|
|
def _insert(self, entity: Insert) -> None:
|
|
self.code.blocks.add(entity.dxf.name)
|
|
self.add_source_code_lines(
|
|
self.api_call(
|
|
"add_blockref(", ["name", "insert"], entity.dxfattribs()
|
|
)
|
|
)
|
|
if len(entity.attribs):
|
|
for attrib in entity.attribs:
|
|
dxfattribs = attrib.dxfattribs()
|
|
dxfattribs[
|
|
"layer"
|
|
] = entity.dxf.layer # set ATTRIB layer to same as INSERT
|
|
self.add_source_code_lines(
|
|
self.generic_api_call(
|
|
"ATTRIB", attrib.dxfattribs(), prefix="a = "
|
|
)
|
|
)
|
|
self.add_source_code_line("e.attribs.append(a)")
|
|
|
|
def _mtext(self, entity: MText) -> None:
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("MTEXT", entity.dxfattribs())
|
|
)
|
|
# MTEXT content 'text' is not a single DXF tag and therefore not a DXF
|
|
# attribute
|
|
self.add_source_code_line("e.text = {}".format(json.dumps(entity.text)))
|
|
|
|
def _lwpolyline(self, entity: LWPolyline) -> None:
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("LWPOLYLINE", entity.dxfattribs())
|
|
)
|
|
# lwpolyline points are not DXF attributes
|
|
self.add_list_source_code(
|
|
entity.get_points(), prolog="e.set_points([", epilog="])"
|
|
)
|
|
|
|
def _spline(self, entity: Spline) -> None:
|
|
self.add_source_code_lines(
|
|
self.api_call("add_spline(", ["degree"], entity.dxfattribs())
|
|
)
|
|
# spline points, knots and weights are not DXF attributes
|
|
if len(entity.fit_points):
|
|
self.add_list_source_code(
|
|
entity.fit_points, prolog="e.fit_points = [", epilog="]"
|
|
)
|
|
|
|
if len(entity.control_points):
|
|
self.add_list_source_code(
|
|
entity.control_points, prolog="e.control_points = [", epilog="]"
|
|
)
|
|
|
|
if len(entity.knots):
|
|
self.add_list_source_code(
|
|
entity.knots, prolog="e.knots = [", epilog="]"
|
|
)
|
|
|
|
if len(entity.weights):
|
|
self.add_list_source_code(
|
|
entity.weights, prolog="e.weights = [", epilog="]"
|
|
)
|
|
|
|
def _polyline(self, entity: Polyline) -> None:
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("POLYLINE", entity.dxfattribs())
|
|
)
|
|
# polyline vertices are separate DXF entities and therefore not DXF attributes
|
|
for v in entity.vertices:
|
|
attribs = _purge_handles(v.dxfattribs())
|
|
location = attribs.pop("location")
|
|
if "layer" in attribs:
|
|
del attribs[
|
|
"layer"
|
|
] # layer is automatically set to the POLYLINE layer
|
|
|
|
# each VERTEX can have different DXF attributes: bulge, start_width, end_width ...
|
|
self.add_source_code_line(
|
|
f"e.append_vertex({str(location)}, dxfattribs={attribs})"
|
|
)
|
|
|
|
def _leader(self, entity: Leader):
|
|
self.add_source_code_line(
|
|
"# Dimension style attribute overriding is not supported!"
|
|
)
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("LEADER", entity.dxfattribs())
|
|
)
|
|
self.add_list_source_code(
|
|
entity.vertices, prolog="e.set_vertices([", epilog="])"
|
|
)
|
|
|
|
def _dimension(self, entity: Dimension):
|
|
self.add_import_statement(
|
|
"from ezdxf.dimstyleoverride import DimStyleOverride"
|
|
)
|
|
self.add_source_code_line(
|
|
"# Dimension style attribute overriding is not supported!"
|
|
)
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("DIMENSION", entity.dxfattribs())
|
|
)
|
|
self.add_source_code_lines(
|
|
[
|
|
"# You have to create the required graphical representation for ",
|
|
"# the DIMENSION entity as anonymous block, otherwise the DXF file",
|
|
"# is invalid for AutoCAD (but not for BricsCAD):",
|
|
"# DimStyleOverride(e).render()",
|
|
"",
|
|
]
|
|
)
|
|
|
|
def _image(self, entity: Image):
|
|
self.add_source_code_line(
|
|
"# Image requires IMAGEDEF and IMAGEDEFREACTOR objects in the "
|
|
"OBJECTS section!"
|
|
)
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("IMAGE", entity.dxfattribs())
|
|
)
|
|
if len(entity.boundary_path):
|
|
self.add_list_source_code(
|
|
(v for v in entity.boundary_path), # just x, y axis
|
|
prolog="e.set_boundary_path([",
|
|
epilog="])",
|
|
)
|
|
self.add_source_code_line(
|
|
"# Set valid image_def_handle and image_def_reactor_handle, "
|
|
"otherwise the DXF file is invalid for AutoCAD"
|
|
)
|
|
|
|
def _wipeout(self, entity: Wipeout):
|
|
self.add_source_code_lines(
|
|
self.generic_api_call("WIPEOUT", entity.dxfattribs())
|
|
)
|
|
if len(entity.boundary_path):
|
|
self.add_list_source_code(
|
|
(v for v in entity.boundary_path), # just x, y axis
|
|
prolog="e.set_boundary_path([",
|
|
epilog="])",
|
|
)
|
|
|
|
def _mesh(self, entity: Mesh):
|
|
self.add_source_code_lines(
|
|
self.api_call("add_mesh(", [], entity.dxfattribs())
|
|
)
|
|
if len(entity.vertices):
|
|
self.add_list_source_code(
|
|
entity.vertices, prolog="e.vertices = [", epilog="]"
|
|
)
|
|
if len(entity.edges):
|
|
# array.array -> tuple
|
|
self.add_list_source_code(
|
|
(tuple(e) for e in entity.edges),
|
|
prolog="e.edges = [",
|
|
epilog="]",
|
|
)
|
|
if len(entity.faces):
|
|
# array.array -> tuple
|
|
self.add_list_source_code(
|
|
(tuple(f) for f in entity.faces),
|
|
prolog="e.faces = [",
|
|
epilog="]",
|
|
)
|
|
if len(entity.creases):
|
|
self.add_list_source_code(
|
|
entity.creases, prolog="e.creases = [", epilog="]"
|
|
)
|
|
|
|
def _hatch(self, entity: Hatch):
|
|
dxfattribs = entity.dxfattribs()
|
|
dxfattribs["associative"] = 0 # associative hatch not supported
|
|
self.add_source_code_lines(
|
|
self.api_call("add_hatch(", ["color"], dxfattribs)
|
|
)
|
|
self._polygon(entity)
|
|
|
|
def _mpolygon(self, entity: MPolygon):
|
|
dxfattribs = entity.dxfattribs()
|
|
self.add_source_code_lines(
|
|
self.api_call("add_mpolygon(", ["color"], dxfattribs)
|
|
)
|
|
if entity.dxf.solid_fill:
|
|
self.add_source_code_line(
|
|
f"e.set_solid_fill(color={entity.dxf.fill_color})\n"
|
|
)
|
|
self._polygon(entity)
|
|
|
|
def _polygon(self, entity: DXFPolygon):
|
|
add_line = self.add_source_code_line
|
|
if len(entity.seeds):
|
|
add_line(f"e.set_seed_points({entity.seeds})")
|
|
if entity.pattern:
|
|
self.add_list_source_code(
|
|
map(str, entity.pattern.lines),
|
|
prolog="e.set_pattern_definition([",
|
|
epilog="])",
|
|
)
|
|
self.add_source_code_line("e.dxf.solid_fill = 0")
|
|
arg = " {}={},"
|
|
|
|
if entity.gradient is not None:
|
|
g = entity.gradient
|
|
add_line("e.set_gradient(")
|
|
add_line(arg.format("color1", str(g.color1)))
|
|
add_line(arg.format("color2", str(g.color2)))
|
|
add_line(arg.format("rotation", g.rotation))
|
|
add_line(arg.format("centered", g.centered))
|
|
add_line(arg.format("one_color", g.one_color))
|
|
add_line(arg.format("name", json.dumps(g.name)))
|
|
add_line(")")
|
|
for count, path in enumerate(entity.paths, start=1):
|
|
if path.type == BoundaryPathType.POLYLINE:
|
|
add_line("# {}. polyline path".format(count))
|
|
self.add_list_source_code(
|
|
path.vertices,
|
|
prolog="e.paths.add_polyline_path([",
|
|
epilog=" ],",
|
|
)
|
|
add_line(arg.format("is_closed", str(path.is_closed)))
|
|
add_line(arg.format("flags", str(path.path_type_flags)))
|
|
add_line(")")
|
|
else: # EdgePath
|
|
add_line(
|
|
f"# {count}. edge path: associative hatch not supported"
|
|
)
|
|
add_line(
|
|
f"ep = e.paths.add_edge_path(flags={path.path_type_flags})"
|
|
)
|
|
for edge in path.edges:
|
|
if edge.type == EdgeType.LINE:
|
|
add_line(f"ep.add_line({edge.start}, {str(edge.end)})")
|
|
elif edge.type == EdgeType.ARC:
|
|
# Start- and end angles are always stored in ccw
|
|
# orientation:
|
|
add_line("ep.add_arc(")
|
|
add_line(arg.format("center", str(edge.center)))
|
|
add_line(arg.format("radius", edge.radius))
|
|
add_line(arg.format("start_angle", edge.start_angle))
|
|
add_line(arg.format("end_angle", edge.end_angle))
|
|
add_line(arg.format("ccw", edge.ccw))
|
|
add_line(")")
|
|
elif edge.type == EdgeType.ELLIPSE:
|
|
# Start- and end params are always stored in ccw
|
|
# orientation:
|
|
add_line("ep.add_ellipse(")
|
|
add_line(arg.format("center", str(edge.center)))
|
|
add_line(arg.format("major_axis", str(edge.major_axis)))
|
|
add_line(arg.format("ratio", edge.ratio))
|
|
add_line(arg.format("start_angle", edge.start_angle))
|
|
add_line(arg.format("end_angle", edge.end_angle))
|
|
add_line(arg.format("ccw", edge.ccw))
|
|
add_line(")")
|
|
elif edge.type == EdgeType.SPLINE:
|
|
add_line("ep.add_spline(")
|
|
if edge.fit_points:
|
|
add_line(
|
|
arg.format(
|
|
"fit_points",
|
|
str([fp for fp in edge.fit_points]),
|
|
)
|
|
)
|
|
if edge.control_points:
|
|
add_line(
|
|
arg.format(
|
|
"control_points",
|
|
str([cp for cp in edge.control_points]),
|
|
)
|
|
)
|
|
if edge.knot_values:
|
|
add_line(
|
|
arg.format("knot_values", str(edge.knot_values))
|
|
)
|
|
if edge.weights:
|
|
add_line(arg.format("weights", str(edge.weights)))
|
|
add_line(arg.format("degree", edge.degree))
|
|
add_line(arg.format("periodic", edge.periodic))
|
|
if edge.start_tangent is not None:
|
|
add_line(
|
|
arg.format(
|
|
"start_tangent", str(edge.start_tangent)
|
|
)
|
|
)
|
|
if edge.end_tangent is not None:
|
|
add_line(
|
|
arg.format("end_tangent", str(edge.end_tangent))
|
|
)
|
|
add_line(")")
|
|
|
|
# simple table entries
|
|
def _layer(self, layer: DXFEntity):
|
|
self.add_source_code_lines(
|
|
self.new_table_entry("LAYER", layer.dxfattribs())
|
|
)
|
|
|
|
def _ltype(self, ltype: Linetype):
|
|
self.add_import_statement("from ezdxf.lldxf.tags import Tags")
|
|
self.add_import_statement("from ezdxf.lldxf.types import dxftag")
|
|
self.add_import_statement(
|
|
"from ezdxf.entities.ltype import LinetypePattern"
|
|
)
|
|
self.add_source_code_lines(
|
|
self.new_table_entry("LTYPE", ltype.dxfattribs())
|
|
)
|
|
self.add_tags_source_code(
|
|
ltype.pattern_tags.tags,
|
|
prolog="tags = Tags([",
|
|
epilog="])",
|
|
indent=4,
|
|
)
|
|
self.add_source_code_line(" t.pattern_tags = LinetypePattern(tags)")
|
|
|
|
def _style(self, style: DXFEntity):
|
|
self.add_source_code_lines(
|
|
self.new_table_entry("STYLE", style.dxfattribs())
|
|
)
|
|
|
|
def _dimstyle(self, dimstyle: DXFEntity):
|
|
self.add_source_code_lines(
|
|
self.new_table_entry("DIMSTYLE", dimstyle.dxfattribs())
|
|
)
|
|
|
|
def _appid(self, appid: DXFEntity):
|
|
self.add_source_code_lines(
|
|
self.new_table_entry("APPID", appid.dxfattribs())
|
|
)
|