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

627 lines
19 KiB
Python

# Copyright (c) 2019-2022 Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import TYPE_CHECKING, Iterable, Iterator
from ezdxf.math import Vec2, Shape2d, NULLVEC, UVec
from .forms import open_arrow, arrow2
if TYPE_CHECKING:
from ezdxf.entities import DXFGraphic
from ezdxf.sections.blocks import BlocksSection
from ezdxf.eztypes import GenericLayoutType
DEFAULT_ARROW_ANGLE = 18.924644
DEFAULT_BETA = 45.0
# The base arrow is oriented for the right hand side ->| of the dimension line,
# reverse is the left hand side |<-.
class BaseArrow:
def __init__(self, vertices: Iterable[UVec]):
self.shape = Shape2d(vertices)
def render(self, layout: GenericLayoutType, dxfattribs=None):
pass
def place(self, insert: UVec, angle: float):
self.shape.rotate(angle)
self.shape.translate(insert)
class NoneStroke(BaseArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
super().__init__([Vec2(insert)])
class ObliqueStroke(BaseArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
self.size = size
s2 = size / 2
# shape = [center, lower left, upper right]
super().__init__([Vec2((-s2, -s2)), Vec2((s2, s2))])
self.place(insert, angle)
def render(self, layout: GenericLayoutType, dxfattribs=None):
layout.add_line(
start=self.shape[0], end=self.shape[1], dxfattribs=dxfattribs
)
class ArchTick(ObliqueStroke):
def render(self, layout: GenericLayoutType, dxfattribs=None):
width = self.size * 0.15
dxfattribs = dxfattribs or {}
if layout.dxfversion > "AC1009":
dxfattribs["const_width"] = width
layout.add_lwpolyline(
self.shape, format="xy", dxfattribs=dxfattribs # type: ignore
)
else:
dxfattribs["default_start_width"] = width
dxfattribs["default_end_width"] = width
layout.add_polyline2d(self.shape, dxfattribs=dxfattribs) # type: ignore
class ClosedArrowBlank(BaseArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
super().__init__(open_arrow(size, angle=DEFAULT_ARROW_ANGLE))
self.place(insert, angle)
def render(self, layout: GenericLayoutType, dxfattribs=None):
if layout.dxfversion > "AC1009":
polyline = layout.add_lwpolyline(
points=self.shape, dxfattribs=dxfattribs # type: ignore
)
else:
polyline = layout.add_polyline2d( # type: ignore
points=self.shape, dxfattribs=dxfattribs # type: ignore
)
polyline.close(True)
class ClosedArrow(ClosedArrowBlank):
def render(self, layout: GenericLayoutType, dxfattribs=None):
super().render(layout, dxfattribs)
end_point = self.shape[0].lerp(self.shape[2])
layout.add_line(
start=self.shape[1], end=end_point, dxfattribs=dxfattribs
)
class ClosedArrowFilled(ClosedArrow):
def render(self, layout: GenericLayoutType, dxfattribs=None):
layout.add_solid(
points=self.shape, # type: ignore
dxfattribs=dxfattribs,
)
class _OpenArrow(BaseArrow):
def __init__(
self,
arrow_angle: float,
insert: UVec,
size: float = 1.0,
angle: float = 0,
):
points = list(open_arrow(size, angle=arrow_angle))
points.append((-1, 0))
super().__init__(points)
self.place(insert, angle)
def render(self, layout: GenericLayoutType, dxfattribs=None):
if layout.dxfversion > "AC1009":
layout.add_lwpolyline(points=self.shape[:-1], dxfattribs=dxfattribs)
else:
layout.add_polyline2d(points=self.shape[:-1], dxfattribs=dxfattribs)
layout.add_line(
start=self.shape[1], end=self.shape[-1], dxfattribs=dxfattribs
)
class OpenArrow(_OpenArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
super().__init__(DEFAULT_ARROW_ANGLE, insert, size, angle)
class OpenArrow30(_OpenArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
super().__init__(30, insert, size, angle)
class OpenArrow90(_OpenArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
super().__init__(90, insert, size, angle)
class Circle(BaseArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
self.radius = size / 2
# shape = [center point, connection point]
super().__init__(
[
Vec2((0, 0)),
Vec2((-self.radius, 0)),
Vec2((-size, 0)),
]
)
self.place(insert, angle)
def render(self, layout: GenericLayoutType, dxfattribs=None):
layout.add_circle(
center=self.shape[0], radius=self.radius, dxfattribs=dxfattribs
)
class Origin(Circle):
def render(self, layout: GenericLayoutType, dxfattribs=None):
super().render(layout, dxfattribs)
layout.add_line(
start=self.shape[0], end=self.shape[2], dxfattribs=dxfattribs
)
class CircleBlank(Circle):
def render(self, layout: GenericLayoutType, dxfattribs=None):
super().render(layout, dxfattribs)
layout.add_line(
start=self.shape[1], end=self.shape[2], dxfattribs=dxfattribs
)
class Origin2(Circle):
def render(self, layout: GenericLayoutType, dxfattribs=None):
layout.add_circle(
center=self.shape[0], radius=self.radius, dxfattribs=dxfattribs
)
layout.add_circle(
center=self.shape[0], radius=self.radius / 2, dxfattribs=dxfattribs
)
layout.add_line(
start=self.shape[1], end=self.shape[2], dxfattribs=dxfattribs
)
class DotSmall(Circle):
def render(self, layout: GenericLayoutType, dxfattribs=None):
center = self.shape[0]
d = Vec2((self.radius / 2, 0))
p1 = center - d
p2 = center + d
dxfattribs = dxfattribs or {}
if layout.dxfversion > "AC1009":
dxfattribs["const_width"] = self.radius
layout.add_lwpolyline(
[(p1, 1), (p2, 1)],
format="vb",
close=True,
dxfattribs=dxfattribs,
)
else:
dxfattribs["default_start_width"] = self.radius
dxfattribs["default_end_width"] = self.radius
polyline = layout.add_polyline2d(
points=[p1, p2], close=True, dxfattribs=dxfattribs
)
polyline[0].dxf.bulge = 1
polyline[1].dxf.bulge = 1
class Dot(DotSmall):
def render(self, layout: GenericLayoutType, dxfattribs=None):
layout.add_line(
start=self.shape[1], end=self.shape[2], dxfattribs=dxfattribs
)
super().render(layout, dxfattribs)
class Box(BaseArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
# shape = [lower_left, lower_right, upper_right, upper_left, connection point]
s2 = size / 2
super().__init__(
[
Vec2((-s2, -s2)),
Vec2((+s2, -s2)),
Vec2((+s2, +s2)),
Vec2((-s2, +s2)),
Vec2((-s2, 0)),
Vec2((-size, 0)),
]
)
self.place(insert, angle)
def render(self, layout: GenericLayoutType, dxfattribs=None):
if layout.dxfversion > "AC1009":
polyline = layout.add_lwpolyline(
points=self.shape[0:4], dxfattribs=dxfattribs
)
else:
polyline = layout.add_polyline2d( # type: ignore
points=self.shape[0:4], dxfattribs=dxfattribs
)
polyline.close(True)
layout.add_line(
start=self.shape[4], end=self.shape[5], dxfattribs=dxfattribs
)
class BoxFilled(Box):
def render(self, layout: GenericLayoutType, dxfattribs=None):
def solid_order():
v = self.shape.vertices
return [v[0], v[1], v[3], v[2]]
layout.add_solid(points=solid_order(), dxfattribs=dxfattribs)
layout.add_line(
start=self.shape[4], end=self.shape[5], dxfattribs=dxfattribs
)
class Integral(BaseArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
self.radius = size * 0.3535534
self.angle = angle
# shape = [center, left_center, right_center]
super().__init__(
[
Vec2((0, 0)),
Vec2((-self.radius, 0)),
Vec2((self.radius, 0)),
]
)
self.place(insert, angle)
def render(self, layout: GenericLayoutType, dxfattribs=None):
angle = self.angle
layout.add_arc(
center=self.shape[1],
radius=self.radius,
start_angle=-90 + angle,
end_angle=angle,
dxfattribs=dxfattribs,
)
layout.add_arc(
center=self.shape[2],
radius=self.radius,
start_angle=90 + angle,
end_angle=180 + angle,
dxfattribs=dxfattribs,
)
class DatumTriangle(BaseArrow):
REVERSE_ANGLE = 180
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
d = 0.577350269 * size # tan(30)
# shape = [upper_corner, lower_corner, connection_point]
super().__init__(
[
Vec2((0, d)),
Vec2((0, -d)),
Vec2((-size, 0)),
]
)
self.place(insert, angle)
def render(self, layout: GenericLayoutType, dxfattribs=None):
if layout.dxfversion > "AC1009":
polyline = layout.add_lwpolyline(
points=self.shape, dxfattribs=dxfattribs # type: ignore
)
else:
polyline = layout.add_polyline2d( # type: ignore
points=self.shape, dxfattribs=dxfattribs # type: ignore
)
polyline.close(True)
class DatumTriangleFilled(DatumTriangle):
def render(self, layout: GenericLayoutType, dxfattribs=None):
layout.add_solid(points=self.shape, dxfattribs=dxfattribs) # type: ignore
class _EzArrow(BaseArrow):
def __init__(self, insert: UVec, size: float = 1.0, angle: float = 0):
points = list(arrow2(size, angle=DEFAULT_ARROW_ANGLE))
points.append((-1, 0))
super().__init__(points)
self.place(insert, angle)
def render(self, layout: GenericLayoutType, dxfattribs=None):
if layout.dxfversion > "AC1009":
polyline = layout.add_lwpolyline(
self.shape[:-1], dxfattribs=dxfattribs
)
else:
polyline = layout.add_polyline2d( # type: ignore
self.shape[:-1], dxfattribs=dxfattribs
)
polyline.close(True)
class EzArrowBlank(_EzArrow):
def render(self, layout: GenericLayoutType, dxfattribs=None):
super().render(layout, dxfattribs)
layout.add_line(
start=self.shape[-2], end=self.shape[-1], dxfattribs=dxfattribs
)
class EzArrow(_EzArrow):
def render(self, layout: GenericLayoutType, dxfattribs=None):
super().render(layout, dxfattribs)
layout.add_line(
start=self.shape[1], end=self.shape[-1], dxfattribs=dxfattribs
)
class EzArrowFilled(_EzArrow):
def render(self, layout: GenericLayoutType, dxfattribs=None):
points = self.shape.vertices
layout.add_solid(
[points[0], points[1], points[3], points[2]], dxfattribs=dxfattribs
)
layout.add_line(
start=self.shape[-2], end=self.shape[-1], dxfattribs=dxfattribs
)
class _Arrows:
closed_filled = ""
dot = "DOT"
dot_small = "DOTSMALL"
dot_blank = "DOTBLANK"
origin_indicator = "ORIGIN"
origin_indicator_2 = "ORIGIN2"
open = "OPEN"
right_angle = "OPEN90"
open_30 = "OPEN30"
closed = "CLOSED"
dot_smallblank = "SMALL"
none = "NONE"
oblique = "OBLIQUE"
box_filled = "BOXFILLED"
box = "BOXBLANK"
closed_blank = "CLOSEDBLANK"
datum_triangle_filled = "DATUMFILLED"
datum_triangle = "DATUMBLANK"
integral = "INTEGRAL"
architectural_tick = "ARCHTICK"
# ezdxf special arrows
ez_arrow = "EZ_ARROW"
ez_arrow_blank = "EZ_ARROW_BLANK"
ez_arrow_filled = "EZ_ARROW_FILLED"
CLASSES = {
closed_filled: ClosedArrowFilled,
dot: Dot,
dot_small: DotSmall,
dot_blank: CircleBlank,
origin_indicator: Origin,
origin_indicator_2: Origin2,
open: OpenArrow,
right_angle: OpenArrow90,
open_30: OpenArrow30,
closed: ClosedArrow,
dot_smallblank: Circle,
none: NoneStroke,
oblique: ObliqueStroke,
box_filled: BoxFilled,
box: Box,
closed_blank: ClosedArrowBlank,
datum_triangle: DatumTriangle,
datum_triangle_filled: DatumTriangleFilled,
integral: Integral,
architectural_tick: ArchTick,
ez_arrow: EzArrow,
ez_arrow_blank: EzArrowBlank,
ez_arrow_filled: EzArrowFilled,
}
# arrows with origin at dimension line start/end
ORIGIN_ZERO = {
architectural_tick,
oblique,
dot_small,
dot_smallblank,
integral,
none,
}
__acad__ = {
closed_filled,
dot,
dot_small,
dot_blank,
origin_indicator,
origin_indicator_2,
open,
right_angle,
open_30,
closed,
dot_smallblank,
none,
oblique,
box_filled,
box,
closed_blank,
datum_triangle,
datum_triangle_filled,
integral,
architectural_tick,
}
__ezdxf__ = {
ez_arrow,
ez_arrow_blank,
ez_arrow_filled,
}
__all_arrows__ = __acad__ | __ezdxf__
EXTENSIONS_ALLOWED = {
architectural_tick,
oblique,
none,
dot_smallblank,
integral,
dot_small,
}
def is_acad_arrow(self, item: str) -> bool:
"""Returns ``True`` if `item` is a standard AutoCAD arrow."""
return item.upper() in self.__acad__
def is_ezdxf_arrow(self, item: str) -> bool:
"""Returns ``True`` if `item` is a special `ezdxf` arrow."""
return item.upper() in self.__ezdxf__
def has_extension_line(self, name):
"""Returns ``True`` if the arrow `name` supports extension lines."""
return name in self.EXTENSIONS_ALLOWED
def __contains__(self, item: str) -> bool:
"""Returns `True` if `item` is an arrow managed by this class."""
if item is None:
return False
return item.upper() in self.__all_arrows__
def create_block(self, blocks: BlocksSection, name: str) -> str:
"""Creates the BLOCK definition for arrow `name`."""
block_name = self.block_name(name)
if block_name not in blocks:
block = blocks.new(block_name)
arrow = self.arrow_shape(name, insert=(0, 0), size=1, rotation=0)
arrow.render(block, dxfattribs={"color": 0, "linetype": "BYBLOCK"})
return block_name
def arrow_handle(self, blocks: BlocksSection, name: str) -> str:
"""Returns the BLOCK_RECORD handle for arrow `name`."""
arrow_name = self.arrow_name(name)
block_name = self.create_block(blocks, arrow_name)
block = blocks.get(block_name)
return block.block_record_handle
def block_name(self, name: str) -> str:
"""Returns the block name."""
if not self.is_acad_arrow(name): # common BLOCK definition
# e.g. Dimension.dxf.bkl = 'EZ_ARROW' == Insert.dxf.name
return name.upper()
elif name == "":
# special AutoCAD arrow symbol 'CLOSED_FILLED' has no name
# ezdxf uses blocks for ALL arrows, but '_' (closed filled) as block name?
return "_CLOSEDFILLED" # Dimension.dxf.bkl = '' != Insert.dxf.name = '_CLOSED_FILLED'
else:
# add preceding '_' to AutoCAD arrow symbol names
# Dimension.dxf.bkl = 'DOT' != Insert.dxf.name = '_DOT'
return "_" + name.upper()
def arrow_name(self, block_name: str) -> str:
"""Returns the arrow name."""
if block_name.startswith("_"):
name = block_name[1:].upper()
if name == "CLOSEDFILLED":
return ""
elif self.is_acad_arrow(name):
return name
return block_name
def insert_arrow(
self,
layout: GenericLayoutType,
name: str,
insert: UVec = NULLVEC,
size: float = 1.0,
rotation: float = 0,
*,
dxfattribs=None,
) -> Vec2:
"""Insert arrow as block reference into `layout`."""
block_name = self.create_block(layout.doc.blocks, name)
dxfattribs = dict(dxfattribs or {})
dxfattribs["rotation"] = rotation
dxfattribs["xscale"] = size
dxfattribs["yscale"] = size
layout.add_blockref(block_name, insert=insert, dxfattribs=dxfattribs)
return connection_point(
name, insert=insert, scale=size, rotation=rotation
)
def render_arrow(
self,
layout: GenericLayoutType,
name: str,
insert: UVec = NULLVEC,
size: float = 1.0,
rotation: float = 0,
*,
dxfattribs=None,
) -> Vec2:
"""Render arrow as basic DXF entities into `layout`."""
dxfattribs = dict(dxfattribs or {})
arrow = self.arrow_shape(name, insert, size, rotation)
arrow.render(layout, dxfattribs)
return connection_point(
name, insert=insert, scale=size, rotation=rotation
)
def virtual_entities(
self,
name: str,
insert: UVec = NULLVEC,
size: float = 0.625,
rotation: float = 0,
*,
dxfattribs=None,
) -> Iterator[DXFGraphic]:
"""Returns all arrow components as virtual DXF entities."""
from ezdxf.layouts import VirtualLayout
if name in self:
layout = VirtualLayout()
ARROWS.render_arrow(
layout,
name,
insert=insert,
size=size,
rotation=rotation,
dxfattribs=dxfattribs,
)
yield from iter(layout)
def arrow_shape(
self, name: str, insert: UVec, size: float, rotation: float
) -> BaseArrow:
"""Returns an instance of the shape management class for arrow `name`."""
# size depending shapes
name = name.upper()
if name == self.dot_small:
size *= 0.25
elif name == self.dot_smallblank:
size *= 0.5
cls = self.CLASSES[name]
return cls(insert, size, rotation)
def connection_point(
arrow_name: str, insert: UVec, scale: float = 1.0, rotation: float = 0.0
) -> Vec2:
"""Returns the connection point for `arrow_name`."""
insert = Vec2(insert)
if ARROWS.arrow_name(arrow_name) in _Arrows.ORIGIN_ZERO:
return insert
else:
return insert - Vec2.from_deg_angle(rotation, scale)
def arrow_length(arrow_name: str, scale: float = 1.0) -> float:
"""Returns the scaled arrow length of `arrow_name`."""
if ARROWS.arrow_name(arrow_name) in _Arrows.ORIGIN_ZERO:
return 0.0
else:
return scale
ARROWS: _Arrows = _Arrows()