1#  Copyright (c) 2020, Manfred Moitzi
2#  License: MIT License
3from typing import Tuple, Iterable
4import math
5from ezdxf.math import Vec3, Vertex
6
7LineSegment = Tuple[Vec3, Vec3]
8
9
10class LineTypeRenderer:
11    def __init__(self, dashes: Iterable[float]):
12        # Simplified dash pattern: line-gap-line-gap
13        # Dash pattern should end with a gap (even count).
14        # Dash length in drawing units.
15
16        self._dashes = tuple(dashes)
17        self._dash_count = len(self._dashes)
18        self.is_solid = True
19        self._current_dash = 0
20        self._current_dash_length = 0
21        if self._dash_count > 1:
22            self.is_solid = False
23            self._current_dash_length = self._dashes[0]
24            self._is_dash = True
25
26    def line_segment(
27            self, start: Vertex, end: Vertex) -> Iterable[LineSegment]:
28        start = Vec3(start)
29        end = Vec3(end)
30        if self.is_solid or start.isclose(end):
31            yield start, end
32            return
33
34        segment_vec = end - start
35        segment_length = segment_vec.magnitude
36        segment_dir = segment_vec / segment_length  # normalize
37
38        for is_dash, dash_length in self._render_dashes(segment_length):
39            end = start + segment_dir * dash_length
40            if is_dash:
41                yield start, end
42            start = end
43
44    def line_segments(
45            self, vertices: Iterable[Vertex]) -> Iterable[LineSegment]:
46        last = None
47        for vertex in vertices:
48            if last is not None:
49                yield from self.line_segment(last, vertex)
50            last = vertex
51
52    def _render_dashes(self, length: float) -> Tuple[bool, float]:
53        if length <= self._current_dash_length:
54            self._current_dash_length -= length
55            yield self._is_dash, length
56            if math.isclose(self._current_dash_length, 0.0):
57                self._cycle_dashes()
58        else:
59            # Avoid deep recursions!
60            while length > self._current_dash_length:
61                length -= self._current_dash_length
62                yield from self._render_dashes(self._current_dash_length)
63            if length > 0.0:
64                yield from self._render_dashes(length)
65
66    def _cycle_dashes(self):
67        self._current_dash = (self._current_dash + 1) % self._dash_count
68        self._current_dash_length = self._dashes[self._current_dash]
69        self._is_dash = not self._is_dash
70