123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- # created: 2019-01-03
- # Copyright (c) 2019 Manfred Moitzi
- # License: MIT License
- from typing import TYPE_CHECKING, Iterable
- from ezdxf.math import Vec2, Shape2d
- from .forms import open_arrow, arrow2
- if TYPE_CHECKING:
- from ezdxf.eztypes import Vertex, GenericLayoutType
- DEFAULT_ARROW_ANGLE = 18.924644
- DEFAULT_BETA = 45.
- # The base Arrow is for the 'Right' side ->| of the Dimension oriented, reverse is the 'Left' side |<-.
- class BaseArrow:
- def __init__(self, vertices: Iterable['Vertex']):
- self.shape = Shape2d(vertices)
- def render(self, layout: 'GenericLayoutType', dxfattribs: dict = None):
- pass
- def place(self, insert: 'Vertex', angle: float):
- self.shape.rotate(angle)
- self.shape.translate(insert)
- class NoneStroke(BaseArrow):
- def __init__(self, insert: 'Vertex', size: float = 1.0, angle: float = 0):
- super().__init__([Vec2(insert)])
- class ObliqueStroke(BaseArrow):
- def __init__(self, insert: 'Vertex', 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: dict = None):
- layout.add_line(start=self.shape[0], end=self.shape[1], dxfattribs=dxfattribs)
- class ArchTick(ObliqueStroke):
- def render(self, layout: 'GenericLayoutType', dxfattribs: dict = None):
- width = self.size * .15
- if layout.dxfversion > 'AC1009':
- dxfattribs['const_width'] = width
- layout.add_lwpolyline(self.shape, format='xy', dxfattribs=dxfattribs)
- else:
- dxfattribs['default_start_width'] = width
- dxfattribs['default_end_width'] = width
- layout.add_polyline2d(self.shape, dxfattribs=dxfattribs)
- class ClosedArrowBlank(BaseArrow):
- def __init__(self, insert: 'Vertex', 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: dict = None):
- if layout.dxfversion > 'AC1009':
- polyline = layout.add_lwpolyline(
- points=self.shape,
- dxfattribs=dxfattribs)
- else:
- polyline = layout.add_polyline2d(
- points=self.shape,
- dxfattribs=dxfattribs)
- polyline.close(True)
- class ClosedArrow(ClosedArrowBlank):
- def render(self, layout: 'GenericLayoutType', dxfattribs: dict = 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: dict = None):
- layout.add_solid(
- points=self.shape,
- dxfattribs=dxfattribs,
- )
- class _OpenArrow(BaseArrow):
- def __init__(self, arrow_angle: float, insert: 'Vertex', 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: dict = 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: 'Vertex', size: float = 1.0, angle: float = 0):
- super().__init__(DEFAULT_ARROW_ANGLE, insert, size, angle)
- class OpenArrow30(_OpenArrow):
- def __init__(self, insert: 'Vertex', size: float = 1.0, angle: float = 0):
- super().__init__(30, insert, size, angle)
- class OpenArrow90(_OpenArrow):
- def __init__(self, insert: 'Vertex', size: float = 1.0, angle: float = 0):
- super().__init__(90, insert, size, angle)
- class Circle(BaseArrow):
- def __init__(self, insert: 'Vertex', 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: dict = None):
- layout.add_circle(center=self.shape[0], radius=self.radius, dxfattribs=dxfattribs)
- class Origin(Circle):
- def render(self, layout: 'GenericLayoutType', dxfattribs: dict = 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: dict = 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: dict = 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: dict = None):
- dxfattribs['closed'] = True
- center = self.shape[0]
- d = Vec2((self.radius / 2, 0))
- p1 = center - d
- p2 = center + d
- if layout.dxfversion > 'AC1009':
- dxfattribs['const_width'] = self.radius
- layout.add_lwpolyline([(p1, 1), (p2, 1)], format='vb', dxfattribs=dxfattribs)
- else:
- dxfattribs['default_start_width'] = self.radius
- dxfattribs['default_end_width'] = self.radius
- polyline = layout.add_polyline2d(points=[p1, p2], dxfattribs=dxfattribs)
- polyline[0].dxf.bulge = 1
- polyline[1].dxf.bulge = 1
- class Dot(DotSmall):
- def render(self, layout: 'GenericLayoutType', dxfattribs: dict = 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: 'Vertex', 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: dict = None):
- if layout.dxfversion > 'AC1009':
- polyline = layout.add_lwpolyline(points=self.shape[0:4], dxfattribs=dxfattribs)
- else:
- polyline = layout.add_polyline2d(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: dict = 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: 'Vertex', size: float = 1.0, angle: float = 0):
- self.radius = size * .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: dict = 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: 'Vertex', size: float = 1.0, angle: float = 0):
- d = .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: dict = None):
- if layout.dxfversion > 'AC1009':
- polyline = layout.add_lwpolyline(points=self.shape, dxfattribs=dxfattribs)
- else:
- polyline = layout.add_polyline2d(points=self.shape, dxfattribs=dxfattribs)
- polyline.close(True)
- class DatumTriangleFilled(DatumTriangle):
- def render(self, layout: 'GenericLayoutType', dxfattribs: dict = None):
- layout.add_solid(points=self.shape, dxfattribs=dxfattribs)
- class _EzArrow(BaseArrow):
- def __init__(self, insert: 'Vertex', 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: dict = None):
- if layout.dxfversion > 'AC1009':
- polyline = layout.add_lwpolyline(self.shape[:-1], dxfattribs=dxfattribs)
- else:
- polyline = layout.add_polyline2d(self.shape[:-1], dxfattribs=dxfattribs)
- polyline.close(True)
- class EzArrowBlank(_EzArrow):
- def render(self, layout: 'GenericLayoutType', dxfattribs: dict = 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: dict = 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: dict = 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:
- return item.upper() in self.__acad__
- def is_ezdxf_arrow(self, item: str) -> bool:
- return item.upper() in self.__ezdxf__
- def has_extension_line(self, name):
- return name in self.EXTENSIONS_ALLOWED
- def __contains__(self, item: str) -> bool:
- if item is None:
- return False
- return item.upper() in self.__all_arrows__
- def create_block(self, blocks, name: str):
- 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 block_name(self, name):
- if not self.is_acad_arrow(name): # common BLOCK definition
- return name.upper() # e.g. Dimension.dxf.bkl = 'EZ_ARROW' == Insert.dxf.name
- elif name == "": # special AutoCAD arrow symbols '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: # special AutoCAD arrow symbols have leading '_' as common practice!
- return '_' + name.upper() # Dimension.dxf.bkl = 'DOT' != Insert.dxf.name = '_DOT'
- def arrow_name(self, block_name: str) -> str:
- 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: 'Vertex',
- size: float = 1.,
- rotation: float = 0,
- dxfattribs: dict = None) -> Vec2:
- block_name = self.create_block(layout.drawing.blocks, name)
- dxfattribs = dict(dxfattribs) if dxfattribs else {} # copy attribs
- 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: 'Vertex',
- size: float = 1.,
- rotation: float = 0,
- dxfattribs: dict = None) -> Vec2:
- dxfattribs = 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 arrow_shape(self, name: str, insert: 'Vertex', size: float, rotation: float) -> BaseArrow:
- # size depending shapes
- if name == self.dot_small:
- size *= .25
- elif name == self.dot_smallblank:
- size *= .5
- cls = self.CLASSES[name]
- return cls(insert, size, rotation)
- def connection_point(arrow_name: str, insert: 'Vertex', scale: float = 1, rotation: float = 0) -> Vec2:
- insert = Vec2(insert)
- if arrow_name in _Arrows.ORIGIN_ZERO:
- return insert
- else:
- return insert - Vec2.from_deg_angle(rotation, scale)
- ARROWS = _Arrows()
|