initial
This commit is contained in:
640
.venv/lib/python3.12/site-packages/ezdxf/disassemble.py
Normal file
640
.venv/lib/python3.12/site-packages/ezdxf/disassemble.py
Normal file
@@ -0,0 +1,640 @@
|
||||
# Copyright (c) 2021-2023, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from __future__ import annotations
|
||||
from typing import Iterable, Optional, cast, TYPE_CHECKING
|
||||
import abc
|
||||
import math
|
||||
from ezdxf.entities import DXFEntity, Insert, get_font_name
|
||||
|
||||
from ezdxf.lldxf import const
|
||||
from ezdxf.enums import TextEntityAlignment
|
||||
from ezdxf.math import Vec3, UCS, Z_AXIS, X_AXIS, BoundingBox
|
||||
from ezdxf.path import Path, make_path, from_vertices, precise_bbox
|
||||
from ezdxf.render import MeshBuilder, MeshVertexMerger, TraceBuilder
|
||||
from ezdxf.protocols import SupportsVirtualEntities, virtual_entities
|
||||
|
||||
from ezdxf.tools.text import (
|
||||
TextLine,
|
||||
unified_alignment,
|
||||
plain_text,
|
||||
text_wrap,
|
||||
estimate_mtext_content_extents,
|
||||
)
|
||||
from ezdxf.fonts import fonts
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.entities import LWPolyline, Polyline, MText, Text
|
||||
|
||||
__all__ = [
|
||||
"make_primitive",
|
||||
"recursive_decompose",
|
||||
"to_primitives",
|
||||
"to_vertices",
|
||||
"to_control_vertices",
|
||||
"to_paths",
|
||||
"to_meshes",
|
||||
]
|
||||
|
||||
|
||||
class Primitive(abc.ABC):
|
||||
"""It is not efficient to create the Path() or MeshBuilder() representation
|
||||
by default. For some entities it's just not needed (LINE, POINT) and for
|
||||
others the builtin flattening() method is more efficient or accurate than
|
||||
using a Path() proxy object. (ARC, CIRCLE, ELLIPSE, SPLINE).
|
||||
|
||||
The `max_flattening_distance` defines the max distance between the
|
||||
approximation line and the original curve. Use argument
|
||||
`max_flattening_distance` to override the default value, or set the value
|
||||
by direct attribute access.
|
||||
|
||||
"""
|
||||
|
||||
max_flattening_distance: float = 0.01
|
||||
|
||||
def __init__(
|
||||
self, entity: DXFEntity, max_flattening_distance: Optional[float] = None
|
||||
):
|
||||
self.entity: DXFEntity = entity
|
||||
# Path representation for linear entities:
|
||||
self._path: Optional[Path] = None
|
||||
# MeshBuilder representation for mesh based entities:
|
||||
# PolygonMesh, PolyFaceMesh, Mesh
|
||||
self._mesh: Optional[MeshBuilder] = None
|
||||
if max_flattening_distance is not None:
|
||||
self.max_flattening_distance = max_flattening_distance
|
||||
|
||||
@property
|
||||
def is_empty(self) -> bool:
|
||||
"""Returns `True` if represents an empty primitive which do not
|
||||
yield any vertices.
|
||||
|
||||
"""
|
||||
if self._mesh:
|
||||
return len(self._mesh.vertices) == 0
|
||||
return self.path is None # on demand calculations!
|
||||
|
||||
@property
|
||||
def path(self) -> Optional[Path]:
|
||||
""":class:`~ezdxf.path.Path` representation or ``None``,
|
||||
idiom to check if is a path representation (could be empty)::
|
||||
|
||||
if primitive.path is not None:
|
||||
process(primitive.path)
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def mesh(self) -> Optional[MeshBuilder]:
|
||||
""":class:`~ezdxf.render.mesh.MeshBuilder` representation or ``None``,
|
||||
idiom to check if is a mesh representation (could be empty)::
|
||||
|
||||
if primitive.mesh is not None:
|
||||
process(primitive.mesh)
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
@abc.abstractmethod
|
||||
def vertices(self) -> Iterable[Vec3]:
|
||||
"""Yields all vertices of the path/mesh representation as
|
||||
:class:`~ezdxf.math.Vec3` objects.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def bbox(self, fast=False) -> BoundingBox:
|
||||
"""Returns the :class:`~ezdxf.math.BoundingBox` of the path/mesh
|
||||
representation. Returns the precise bounding box for the path
|
||||
representation if `fast` is ``False``, otherwise the bounding box for
|
||||
Bézier curves is based on their control points.
|
||||
|
||||
"""
|
||||
if self.mesh:
|
||||
return BoundingBox(self.vertices())
|
||||
path = self.path
|
||||
if path:
|
||||
if fast:
|
||||
return BoundingBox(path.control_vertices())
|
||||
return precise_bbox(path)
|
||||
return BoundingBox()
|
||||
|
||||
|
||||
class EmptyPrimitive(Primitive):
|
||||
@property
|
||||
def is_empty(self) -> bool:
|
||||
return True
|
||||
|
||||
def vertices(self) -> Iterable[Vec3]:
|
||||
return []
|
||||
|
||||
|
||||
class ConvertedPrimitive(Primitive):
|
||||
"""Base class for all DXF entities which store the path/mesh representation
|
||||
at instantiation.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, entity: DXFEntity):
|
||||
super().__init__(entity)
|
||||
self._convert_entity()
|
||||
|
||||
@abc.abstractmethod
|
||||
def _convert_entity(self):
|
||||
"""This method creates the path/mesh representation."""
|
||||
pass
|
||||
|
||||
@property
|
||||
def path(self) -> Optional[Path]:
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def mesh(self) -> Optional[MeshBuilder]:
|
||||
return self._mesh
|
||||
|
||||
def vertices(self) -> Iterable[Vec3]:
|
||||
if self.path:
|
||||
yield from self._path.flattening(self.max_flattening_distance) # type: ignore
|
||||
elif self.mesh:
|
||||
yield from self._mesh.vertices # type: ignore
|
||||
|
||||
|
||||
class CurvePrimitive(Primitive):
|
||||
@property
|
||||
def path(self) -> Optional[Path]:
|
||||
"""Create path representation on demand."""
|
||||
if self._path is None:
|
||||
self._path = make_path(self.entity)
|
||||
return self._path
|
||||
|
||||
def vertices(self) -> Iterable[Vec3]:
|
||||
# Not faster but more precise, because cubic bezier curves do not
|
||||
# perfectly represent elliptic arcs (CIRCLE, ARC, ELLIPSE).
|
||||
# SPLINE: cubic bezier curves do not perfectly represent splines with
|
||||
# degree != 3.
|
||||
yield from self.entity.flattening(self.max_flattening_distance) # type: ignore
|
||||
|
||||
|
||||
class LinePrimitive(Primitive):
|
||||
# TODO: apply thickness if not 0
|
||||
@property
|
||||
def path(self) -> Optional[Path]:
|
||||
"""Create path representation on demand."""
|
||||
if self._path is None:
|
||||
self._path = make_path(self.entity)
|
||||
return self._path
|
||||
|
||||
def vertices(self) -> Iterable[Vec3]:
|
||||
e = self.entity
|
||||
yield e.dxf.start
|
||||
yield e.dxf.end
|
||||
|
||||
def bbox(self, fast=False) -> BoundingBox:
|
||||
e = self.entity
|
||||
return BoundingBox((e.dxf.start, e.dxf.end))
|
||||
|
||||
|
||||
class LwPolylinePrimitive(ConvertedPrimitive):
|
||||
# TODO: apply thickness if not 0
|
||||
def _convert_entity(self) -> None:
|
||||
e: LWPolyline = cast("LWPolyline", self.entity)
|
||||
if e.has_width: # use a mesh representation:
|
||||
# TraceBuilder operates in OCS!
|
||||
ocs = e.ocs()
|
||||
elevation = e.dxf.elevation
|
||||
tb = TraceBuilder.from_polyline(e)
|
||||
mb = MeshVertexMerger() # merges coincident vertices
|
||||
for face in tb.faces_wcs(ocs, elevation):
|
||||
mb.add_face(face)
|
||||
self._mesh = MeshBuilder.from_builder(mb)
|
||||
else: # use a path representation to support bulges!
|
||||
self._path = make_path(e)
|
||||
|
||||
|
||||
class PointPrimitive(Primitive):
|
||||
@property
|
||||
def path(self) -> Optional[Path]:
|
||||
"""Create path representation on demand.
|
||||
|
||||
:class:`Path` can not represent a point, a :class:`Path` with only a
|
||||
start point yields not vertices!
|
||||
|
||||
"""
|
||||
if self._path is None:
|
||||
self._path = Path(self.entity.dxf.location)
|
||||
return self._path
|
||||
|
||||
def vertices(self) -> Iterable[Vec3]:
|
||||
yield self.entity.dxf.location
|
||||
|
||||
def bbox(self, fast=False) -> BoundingBox:
|
||||
return BoundingBox((self.entity.dxf.location,))
|
||||
|
||||
|
||||
class MeshPrimitive(ConvertedPrimitive):
|
||||
def _convert_entity(self):
|
||||
self._mesh = MeshBuilder.from_mesh(self.entity)
|
||||
|
||||
|
||||
class QuadrilateralPrimitive(ConvertedPrimitive):
|
||||
# TODO: apply thickness if not 0
|
||||
def _convert_entity(self):
|
||||
self._path = make_path(self.entity)
|
||||
|
||||
|
||||
class PolylinePrimitive(ConvertedPrimitive):
|
||||
# TODO: apply thickness if not 0
|
||||
def _convert_entity(self) -> None:
|
||||
e: Polyline = cast("Polyline", self.entity)
|
||||
if e.is_2d_polyline and e.has_width:
|
||||
# TraceBuilder operates in OCS!
|
||||
ocs = e.ocs()
|
||||
elevation = e.dxf.elevation.z
|
||||
tb = TraceBuilder.from_polyline(e)
|
||||
mb = MeshVertexMerger() # merges coincident vertices
|
||||
for face in tb.faces_wcs(ocs, elevation):
|
||||
mb.add_face(face)
|
||||
self._mesh = MeshBuilder.from_builder(mb)
|
||||
elif e.is_2d_polyline or e.is_3d_polyline:
|
||||
self._path = make_path(e)
|
||||
else:
|
||||
m = MeshVertexMerger.from_polyface(e) # type: ignore
|
||||
self._mesh = MeshBuilder.from_builder(m)
|
||||
|
||||
|
||||
class HatchPrimitive(ConvertedPrimitive):
|
||||
def _convert_entity(self):
|
||||
self._path = make_path(self.entity)
|
||||
|
||||
|
||||
DESCENDER_FACTOR = 0.333 # from TXT SHX font - just guessing
|
||||
X_HEIGHT_FACTOR = 0.666 # from TXT SHX font - just guessing
|
||||
|
||||
|
||||
class TextLinePrimitive(ConvertedPrimitive):
|
||||
def _convert_entity(self) -> None:
|
||||
"""Calculates the rough border path for a single line text.
|
||||
|
||||
Calculation is based on a monospaced font and therefore the border
|
||||
path is just an educated guess.
|
||||
|
||||
Vertical text generation and oblique angle is ignored.
|
||||
|
||||
"""
|
||||
|
||||
def text_rotation():
|
||||
if fit_or_aligned and not p1.isclose(p2):
|
||||
return (p2 - p1).angle
|
||||
else:
|
||||
return math.radians(text.dxf.rotation)
|
||||
|
||||
def location():
|
||||
if fit_or_aligned:
|
||||
return p1.lerp(p2, factor=0.5)
|
||||
return p1
|
||||
|
||||
text = cast("Text", self.entity)
|
||||
if text.dxftype() == "ATTDEF":
|
||||
# ATTDEF outside of a BLOCK renders the tag rather than the value
|
||||
content = text.dxf.tag
|
||||
else:
|
||||
content = text.dxf.text
|
||||
|
||||
content = plain_text(content)
|
||||
if len(content) == 0:
|
||||
# empty path - does not render any vertices!
|
||||
self._path = Path()
|
||||
return
|
||||
|
||||
font = fonts.make_font(
|
||||
get_font_name(text), text.dxf.height, text.dxf.width
|
||||
)
|
||||
text_line = TextLine(content, font)
|
||||
alignment, p1, p2 = text.get_placement()
|
||||
if p2 is None:
|
||||
p2 = p1
|
||||
fit_or_aligned = (
|
||||
alignment == TextEntityAlignment.FIT
|
||||
or alignment == TextEntityAlignment.ALIGNED
|
||||
)
|
||||
if text.dxf.halign > 2: # ALIGNED=3, MIDDLE=4, FIT=5
|
||||
text_line.stretch(alignment, p1, p2)
|
||||
halign, valign = unified_alignment(text)
|
||||
mirror_x = -1 if text.is_backward else 1
|
||||
mirror_y = -1 if text.is_upside_down else 1
|
||||
oblique: float = math.radians(text.dxf.oblique)
|
||||
corner_vertices = text_line.corner_vertices(
|
||||
location(),
|
||||
halign,
|
||||
valign,
|
||||
angle=text_rotation(),
|
||||
scale=(mirror_x, mirror_y),
|
||||
oblique=oblique,
|
||||
)
|
||||
|
||||
ocs = text.ocs()
|
||||
self._path = from_vertices(
|
||||
ocs.points_to_wcs(corner_vertices),
|
||||
close=True,
|
||||
)
|
||||
|
||||
|
||||
class MTextPrimitive(ConvertedPrimitive):
|
||||
def _convert_entity(self) -> None:
|
||||
"""Calculates the rough border path for a MTEXT entity.
|
||||
|
||||
Calculation is based on a mono-spaced font and therefore the border
|
||||
path is just an educated guess.
|
||||
|
||||
Most special features of MTEXT is not supported.
|
||||
|
||||
"""
|
||||
|
||||
def get_content() -> list[str]:
|
||||
text = mtext.plain_text(split=False)
|
||||
return text_wrap(text, box_width, font.text_width) # type: ignore
|
||||
|
||||
def get_max_str() -> str:
|
||||
return max(content, key=lambda s: len(s))
|
||||
|
||||
def get_rect_width() -> float:
|
||||
if box_width:
|
||||
return box_width
|
||||
s = get_max_str()
|
||||
if len(s) == 0:
|
||||
s = " "
|
||||
return font.text_width(s)
|
||||
|
||||
def get_rect_height() -> float:
|
||||
line_height = font.measurements.total_height
|
||||
cap_height = font.measurements.cap_height
|
||||
# Line spacing factor: Percentage of default (3-on-5) line
|
||||
# spacing to be applied.
|
||||
|
||||
# thx to mbway: multiple of cap_height between the baseline of the
|
||||
# previous line and the baseline of the next line
|
||||
# 3-on-5 line spacing = 5/3 = 1.67
|
||||
line_spacing = cap_height * mtext.dxf.line_spacing_factor * 1.67
|
||||
spacing = line_spacing - line_height
|
||||
line_count = len(content)
|
||||
return line_height * line_count + spacing * (line_count - 1)
|
||||
|
||||
def get_ucs() -> UCS:
|
||||
"""Create local coordinate system:
|
||||
origin = insertion point
|
||||
z-axis = extrusion vector
|
||||
x-axis = text_direction or text rotation, text rotation requires
|
||||
extrusion vector == (0, 0, 1) or treatment like an OCS?
|
||||
|
||||
"""
|
||||
origin = mtext.dxf.insert
|
||||
z_axis = mtext.dxf.extrusion # default is Z_AXIS
|
||||
x_axis = X_AXIS
|
||||
if mtext.dxf.hasattr("text_direction"):
|
||||
x_axis = mtext.dxf.text_direction
|
||||
elif mtext.dxf.hasattr("rotation"):
|
||||
# TODO: what if extrusion vector is not (0, 0, 1)
|
||||
x_axis = Vec3.from_deg_angle(mtext.dxf.rotation)
|
||||
z_axis = Z_AXIS
|
||||
return UCS(origin=origin, ux=x_axis, uz=z_axis)
|
||||
|
||||
def get_shift_factors():
|
||||
halign, valign = unified_alignment(mtext)
|
||||
shift_x = 0
|
||||
shift_y = 0
|
||||
if halign == const.CENTER:
|
||||
shift_x = -0.5
|
||||
elif halign == const.RIGHT:
|
||||
shift_x = -1.0
|
||||
if valign == const.MIDDLE:
|
||||
shift_y = 0.5
|
||||
elif valign == const.BOTTOM:
|
||||
shift_y = 1.0
|
||||
return shift_x, shift_y
|
||||
|
||||
def get_corner_vertices() -> Iterable[Vec3]:
|
||||
"""Create corner vertices in the local working plan, where
|
||||
the insertion point is the origin.
|
||||
"""
|
||||
if columns:
|
||||
rect_width = columns.total_width
|
||||
rect_height = columns.total_height
|
||||
if rect_width == 0.0 or rect_height == 0.0:
|
||||
# Reliable sources like AutoCAD and BricsCAD do write
|
||||
# correct total_width and total_height values!
|
||||
w, h = _estimate_column_extents(mtext)
|
||||
if rect_width == 0.0:
|
||||
rect_width = w
|
||||
if rect_height == 0.0:
|
||||
rect_height = h
|
||||
else:
|
||||
rect_width = mtext.dxf.get("rect_width", get_rect_width())
|
||||
rect_height = mtext.dxf.get("rect_height", get_rect_height())
|
||||
# TOP LEFT alignment:
|
||||
vertices = [
|
||||
Vec3(0, 0),
|
||||
Vec3(rect_width, 0),
|
||||
Vec3(rect_width, -rect_height),
|
||||
Vec3(0, -rect_height),
|
||||
]
|
||||
sx, sy = get_shift_factors()
|
||||
shift = Vec3(sx * rect_width, sy * rect_height)
|
||||
return (v + shift for v in vertices)
|
||||
|
||||
mtext: MText = cast("MText", self.entity)
|
||||
columns = mtext.columns
|
||||
if columns is None:
|
||||
box_width = mtext.dxf.get("width", 0)
|
||||
font = fonts.make_font(
|
||||
get_font_name(mtext), mtext.dxf.char_height, 1.0
|
||||
)
|
||||
content: list[str] = get_content()
|
||||
if len(content) == 0:
|
||||
# empty path - does not render any vertices!
|
||||
self._path = Path()
|
||||
return
|
||||
ucs = get_ucs()
|
||||
corner_vertices = get_corner_vertices()
|
||||
self._path = from_vertices(
|
||||
ucs.points_to_wcs(corner_vertices),
|
||||
close=True,
|
||||
)
|
||||
|
||||
|
||||
def _estimate_column_extents(mtext: MText):
|
||||
columns = mtext.columns
|
||||
assert columns is not None
|
||||
_content = mtext.text
|
||||
if columns.count > 1:
|
||||
_columns_content = _content.split("\\N")
|
||||
if len(_columns_content) > 1:
|
||||
_content = max(_columns_content, key=lambda t: len(t))
|
||||
return estimate_mtext_content_extents(
|
||||
content=_content,
|
||||
font=fonts.MonospaceFont(mtext.dxf.char_height, 1.0),
|
||||
column_width=columns.width,
|
||||
line_spacing_factor=mtext.dxf.get_default("line_spacing_factor"),
|
||||
)
|
||||
|
||||
|
||||
class ImagePrimitive(ConvertedPrimitive):
|
||||
def _convert_entity(self):
|
||||
self._path = make_path(self.entity)
|
||||
|
||||
|
||||
class ViewportPrimitive(ConvertedPrimitive):
|
||||
def _convert_entity(self):
|
||||
vp = self.entity
|
||||
if vp.dxf.status == 0: # Viewport is off
|
||||
return # empty primitive
|
||||
self._path = make_path(vp)
|
||||
|
||||
|
||||
# SHAPE is not supported, could not create any SHAPE entities in BricsCAD
|
||||
_PRIMITIVE_CLASSES = {
|
||||
"3DFACE": QuadrilateralPrimitive,
|
||||
"ARC": CurvePrimitive,
|
||||
# TODO: ATTRIB and ATTDEF could contain embedded MTEXT,
|
||||
# but this is not supported yet!
|
||||
"ATTRIB": TextLinePrimitive,
|
||||
"ATTDEF": TextLinePrimitive,
|
||||
"CIRCLE": CurvePrimitive,
|
||||
"ELLIPSE": CurvePrimitive,
|
||||
"HATCH": HatchPrimitive, # multi-path object
|
||||
"MPOLYGON": HatchPrimitive, # multi-path object
|
||||
"HELIX": CurvePrimitive,
|
||||
"IMAGE": ImagePrimitive,
|
||||
"LINE": LinePrimitive,
|
||||
"LWPOLYLINE": LwPolylinePrimitive,
|
||||
"MESH": MeshPrimitive,
|
||||
"MTEXT": MTextPrimitive,
|
||||
"POINT": PointPrimitive,
|
||||
"POLYLINE": PolylinePrimitive,
|
||||
"SPLINE": CurvePrimitive,
|
||||
"SOLID": QuadrilateralPrimitive,
|
||||
"TEXT": TextLinePrimitive,
|
||||
"TRACE": QuadrilateralPrimitive,
|
||||
"VIEWPORT": ViewportPrimitive,
|
||||
"WIPEOUT": ImagePrimitive,
|
||||
}
|
||||
|
||||
|
||||
def make_primitive(
|
||||
entity: DXFEntity, max_flattening_distance=None
|
||||
) -> Primitive:
|
||||
"""Factory to create path/mesh primitives. The `max_flattening_distance`
|
||||
defines the max distance between the approximation line and the original
|
||||
curve. Use `max_flattening_distance` to override the default value.
|
||||
|
||||
Returns an **empty primitive** for unsupported entities. The `empty` state
|
||||
of a primitive can be checked by the property :attr:`is_empty`.
|
||||
The :attr:`path` and the :attr:`mesh` attributes of an empty primitive
|
||||
are ``None`` and the :meth:`vertices` method yields no vertices.
|
||||
|
||||
"""
|
||||
cls = _PRIMITIVE_CLASSES.get(entity.dxftype(), EmptyPrimitive)
|
||||
primitive = cls(entity)
|
||||
if max_flattening_distance:
|
||||
primitive.max_flattening_distance = max_flattening_distance
|
||||
return primitive
|
||||
|
||||
|
||||
def recursive_decompose(entities: Iterable[DXFEntity]) -> Iterable[DXFEntity]:
|
||||
"""Recursive decomposition of the given DXF entity collection into a flat
|
||||
stream of DXF entities. All block references (INSERT) and entities which provide
|
||||
a :meth:`virtual_entities` method will be disassembled into simple DXF
|
||||
sub-entities, therefore the returned entity stream does not contain any
|
||||
INSERT entity.
|
||||
|
||||
Point entities will **not** be disassembled into DXF sub-entities,
|
||||
as defined by the current point style $PDMODE.
|
||||
|
||||
These entity types include sub-entities and will be decomposed into
|
||||
simple DXF entities:
|
||||
|
||||
- INSERT
|
||||
- DIMENSION
|
||||
- LEADER
|
||||
- MLEADER
|
||||
- MLINE
|
||||
|
||||
Decomposition of XREF, UNDERLAY and ACAD_TABLE entities is not supported.
|
||||
|
||||
This function does not apply the clipping path created by the XCLIP command.
|
||||
The function returns all entities and ignores the clipping path polygon and no
|
||||
entity is clipped.
|
||||
|
||||
"""
|
||||
for entity in entities:
|
||||
if isinstance(entity, Insert):
|
||||
# TODO: emit internal XCLIP marker entity?
|
||||
if entity.mcount > 1:
|
||||
yield from recursive_decompose(entity.multi_insert())
|
||||
else:
|
||||
yield from entity.attribs
|
||||
yield from recursive_decompose(virtual_entities(entity))
|
||||
# has a required __virtual_entities__() to be rendered?
|
||||
elif isinstance(entity, SupportsVirtualEntities):
|
||||
# could contain block references:
|
||||
yield from recursive_decompose(virtual_entities(entity))
|
||||
else:
|
||||
yield entity
|
||||
|
||||
|
||||
def to_primitives(
|
||||
entities: Iterable[DXFEntity],
|
||||
max_flattening_distance: Optional[float] = None,
|
||||
) -> Iterable[Primitive]:
|
||||
"""Yields all DXF entities as path or mesh primitives. Yields
|
||||
unsupported entities as empty primitives, see :func:`make_primitive`.
|
||||
|
||||
Args:
|
||||
entities: iterable of DXF entities
|
||||
max_flattening_distance: override the default value
|
||||
|
||||
"""
|
||||
for e in entities:
|
||||
yield make_primitive(e, max_flattening_distance)
|
||||
|
||||
|
||||
def to_vertices(primitives: Iterable[Primitive]) -> Iterable[Vec3]:
|
||||
"""Yields all vertices from the given `primitives`. Paths will be flattened
|
||||
to create the associated vertices. See also :func:`to_control_vertices` to
|
||||
collect only the control vertices from the paths without flattening.
|
||||
|
||||
"""
|
||||
for p in primitives:
|
||||
yield from p.vertices()
|
||||
|
||||
|
||||
def to_paths(primitives: Iterable[Primitive]) -> Iterable[Path]:
|
||||
"""Yields all :class:`~ezdxf.path.Path` objects from the given
|
||||
`primitives`. Ignores primitives without a defined path.
|
||||
|
||||
"""
|
||||
for prim in primitives:
|
||||
if prim.path is not None: # lazy evaluation!
|
||||
yield prim.path
|
||||
|
||||
|
||||
def to_meshes(primitives: Iterable[Primitive]) -> Iterable[MeshBuilder]:
|
||||
"""Yields all :class:`~ezdxf.render.MeshBuilder` objects from the given
|
||||
`primitives`. Ignores primitives without a defined mesh.
|
||||
|
||||
"""
|
||||
for prim in primitives:
|
||||
if prim.mesh is not None:
|
||||
yield prim.mesh
|
||||
|
||||
|
||||
def to_control_vertices(primitives: Iterable[Primitive]) -> Iterable[Vec3]:
|
||||
"""Yields all path control vertices and all mesh vertices from the given
|
||||
`primitives`. Like :func:`to_vertices`, but without flattening.
|
||||
|
||||
"""
|
||||
for prim in primitives:
|
||||
# POINT has only a start point and yields from vertices()!
|
||||
if prim.path:
|
||||
yield from prim.path.control_vertices()
|
||||
else:
|
||||
yield from prim.vertices()
|
||||
Reference in New Issue
Block a user