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

116 lines
3.7 KiB
Python

# Copyright (c) 2024, Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import Sequence, TYPE_CHECKING, Iterable, Any
import math
from ezdxf.math import UVec, Vec2, has_clockwise_orientation
from ezdxf.entities import LWPolyline, DXFEntity
if TYPE_CHECKING:
from ezdxf.layouts import BaseLayout
REVCLOUD_PROPS = "RevcloudProps"
REQUIRED_BULGE = 0.520567050552
def points(
vertices: Iterable[UVec],
segment_length: float,
*,
bulge: float = REQUIRED_BULGE, # required to be recognized as REVCLOUD
start_width: float = 0.0,
end_width: float = 0.0,
) -> list[Sequence[float]]:
"""Returns the points for a :class:`~ezdxf.entities.LWPolyline` entity to render a
revision cloud, similar to the REVCLOUD command in CAD applications.
Args:
vertices: corner points of a polygon
segment_length: approximate segment length
bulge: LWPOLYLINE bulge value
start_width: start width of the segment arc
end_width: end width of the segment arc, CAD applications use 0.1 * segment_length
for a calligraphy effect
"""
if segment_length < 1e-6:
raise ValueError("segment length too small.")
_vertices = _to_vec2_list(vertices)
lw_points: list[Sequence[float]] = []
for s, e in zip(_vertices, _vertices[1:]):
lw_points.append((s.x, s.y, start_width, end_width, bulge))
diff = e - s
length = diff.magnitude
if length <= segment_length:
continue
count = math.ceil(length / segment_length)
_segment_length = length / count
offset = diff.normalize(_segment_length)
for _ in range(count - 1):
s += offset
lw_points.append((s.x, s.y, start_width, end_width, bulge))
return lw_points
def add_entity(
layout: BaseLayout,
vertices: Iterable[UVec],
segment_length: float,
*,
calligraphy=True,
dxfattribs: Any = None,
) -> LWPolyline:
"""Adds a revision cloud as :class:`~ezdxf.entities.LWPolyline` entity to `layout`,
similar to the REVCLOUD command in CAD applications.
Args:
layout: target layout
vertices: corner points of a polygon
segment_length: approximate segment length
calligraphy: ``True`` for a calligraphy effect
dxfattribs: additional DXF attributes
"""
_vertices = _to_vec2_list(vertices)
bulge = REQUIRED_BULGE
if has_clockwise_orientation(_vertices):
bulge = -bulge
end_width = segment_length * 0.1 if calligraphy else 0.0
lw_points = points(_vertices, segment_length, bulge=bulge, end_width=end_width)
lwp = layout.add_lwpolyline(lw_points, close=True, dxfattribs=dxfattribs)
lwp.set_xdata(REVCLOUD_PROPS, [(1070, 0), (1040, segment_length)])
doc = layout.doc
if doc is not None and not doc.appids.has_entry(REVCLOUD_PROPS):
doc.appids.add(REVCLOUD_PROPS)
return lwp
def is_revcloud(entity: DXFEntity) -> bool:
"""Returns ``True`` when the given entity represents a revision cloud."""
if not isinstance(entity, LWPolyline):
return False
lwpolyline: LWPolyline = entity
if not lwpolyline.is_closed:
return False
if not lwpolyline.has_xdata(REVCLOUD_PROPS):
return False
return all(
abs(REQUIRED_BULGE - abs(p[0])) < 0.02
for p in lwpolyline.get_points(format="b")
)
def _to_vec2_list(vertices: Iterable[UVec]) -> list[Vec2]:
_vertices: list[Vec2] = Vec2.list(vertices)
if len(_vertices) < 3:
raise ValueError("3 or more points required.")
if not _vertices[0].isclose(_vertices[-1]):
_vertices.append(_vertices[0])
if len(_vertices) < 4:
raise ValueError("3 or more points required.")
return _vertices