Skip to content

Commit a45aef0

Browse files
fixed the line algorithm
1 parent eaa6ea8 commit a45aef0

File tree

3 files changed

+66
-59
lines changed

3 files changed

+66
-59
lines changed

schemascii/utils.py

+62-43
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Side(IntEnum):
2222
BOTTOM = 3
2323

2424

25-
def colinear(points: list[complex]) -> bool:
25+
def colinear(*points: complex) -> bool:
2626
"Returns true if all the points are in the same line."
2727
return len(set(phase(p - points[0]) for p in points[1:])) == 1
2828

@@ -49,27 +49,45 @@ def intersecting(a, b, p, q):
4949
return sort_a <= sort_p <= sort_b or sort_p <= sort_b <= sort_q
5050

5151

52-
# UNUSED as of yet
53-
def merge_colinear(
54-
points: list[tuple[complex, complex]]
55-
) -> list[tuple[complex, complex]]:
56-
"Merges line segments that are colinear."
57-
points = list(set(points))
58-
out = []
59-
a, b = points[0]
60-
while points:
61-
print(points)
62-
for pq in points[1:]:
63-
p, q = pq
64-
if not (colinear((a, b, p, q)) and intersecting(a, b, p, q)):
52+
def take_next_group(links: list[tuple[complex, complex]]) -> list[tuple[complex, complex]]:
53+
"""Pops the longes possible link off of the `links` list and returns it,
54+
mutating the input list."""
55+
best = [links.pop()]
56+
while True:
57+
for pair in links:
58+
if best[0][0] == pair[1]:
59+
best.insert(0, pair)
60+
links.remove(pair)
61+
elif best[0][0] == pair[0]:
62+
best.insert(0, (pair[1], pair[0]))
63+
links.remove(pair)
64+
elif best[-1][1] == pair[0]:
65+
best.append(pair)
66+
links.remove(pair)
67+
elif best[-1][1] == pair[1]:
68+
best.append((pair[1], pair[0]))
69+
links.remove(pair)
70+
else:
6571
continue
66-
points.remove(pq)
67-
a, b = sorted((a, b, p, q), key=lambda x: x.real)[::3]
6872
break
6973
else:
70-
out.append((a, b))
71-
(a, b), points = points[0], points[1:]
72-
return out
74+
break
75+
return best
76+
77+
78+
def merge_colinear(links: list[tuple[complex, complex]]):
79+
"Merges line segments that are colinear. Mutates the input list."
80+
i = 1
81+
while True:
82+
if i == len(links):
83+
break
84+
elif links[i][0] == links[i][1]:
85+
links.remove(links[i])
86+
elif links[i-1][1] == links[i][0] and colinear(links[i-1][0], links[i][0], links[i][1]):
87+
links[i-1] = (links[i-1][0], links[i][1])
88+
links.remove(links[i])
89+
else:
90+
i += 1
7391

7492

7593
def iterate_line(p1: complex, p2: complex, step: float = 1.0):
@@ -93,7 +111,8 @@ def deep_transform(data, origin: complex, theta: float):
93111
try:
94112
return deep_transform(complex(data), origin, theta)
95113
except TypeError as err:
96-
raise TypeError("bad type to deep_transform(): " + type(data).__name__) from err
114+
raise TypeError("bad type to deep_transform(): " +
115+
type(data).__name__) from err
97116

98117

99118
def fix_number(n: float) -> str:
@@ -114,7 +133,8 @@ def mk_tag(*contents: str, **attrs: str) -> str:
114133
if isinstance(v, float):
115134
v = fix_number(v)
116135
elif isinstance(v, str):
117-
v = re.sub(r"\d+(\.\d+)", lambda m: fix_number(float(m.group())), v)
136+
v = re.sub(r"\d+(\.\d+)",
137+
lambda m: fix_number(float(m.group())), v)
118138
out += f'{k.removesuffix("_").replace("__", "-")}="{v}" '
119139
out = out.rstrip() + ">" + "".join(contents)
120140
return out + f"</{tag}>"
@@ -126,7 +146,7 @@ def mk_tag(*contents: str, **attrs: str) -> str:
126146
del XMLClass
127147

128148

129-
def polylinegon(points: list[complex], is_polygon: bool = False, **options):
149+
def polylinegon(points: list[complex], is_polygon: bool = False, **options) -> str:
130150
"Turn the list of points into a <polyline> or <polygon>."
131151
scale = options["scale"]
132152
w = options["stroke_width"]
@@ -155,20 +175,23 @@ def find_dots(points: list[tuple[complex, complex]]) -> list[complex]:
155175
return [pt for pt, count in seen.items() if count > 3]
156176

157177

158-
def bunch_o_lines(points: list[tuple[complex, complex]], **options):
159-
"Return a <polyline> for each pair of points."
178+
def bunch_o_lines(pairs: list[tuple[complex, complex]], **options) -> str:
179+
"Collapse the pairs of points and return the smallest number of <polyline>s."
160180
out = ""
161181
scale = options["scale"]
162182
w = options["stroke_width"]
163183
c = options["stroke"]
164-
for p1, p2 in points:
165-
if abs(p1 - p2) == 0:
166-
continue
184+
lines = []
185+
while pairs:
186+
group = take_next_group(pairs)
187+
merge_colinear(group)
188+
# make it a polyline
189+
pts = [group[0][0]] + [p[1] for p in group]
190+
lines.append(pts)
191+
for line in lines:
167192
out += XML.polyline(
168-
points=f"{p1.real * scale},"
169-
f"{p1.imag * scale} "
170-
f"{p2.real * scale},"
171-
f"{p2.imag * scale}",
193+
points=" ".join(
194+
f"{p.real * scale},{p.imag * scale}" for p in line),
172195
stroke=c,
173196
stroke__width=w,
174197
)
@@ -182,7 +205,7 @@ def id_text(
182205
unit: str | list[str] | None,
183206
point: complex | None = None,
184207
**options,
185-
):
208+
) -> str:
186209
"Format the component ID and value around the point."
187210
if options["nolabels"]:
188211
return ""
@@ -215,9 +238,11 @@ def id_text(
215238
else "middle"
216239
)
217240
else:
218-
textach = "middle" if terminals[0].side in (Side.TOP, Side.BOTTOM) else "start"
241+
textach = "middle" if terminals[0].side in (
242+
Side.TOP, Side.BOTTOM) else "start"
219243
return XML.text(
220-
(XML.tspan(f"{box.type}{box.id}", class_="cmp-id") * bool("L" in label_style)),
244+
(XML.tspan(f"{box.type}{box.id}", class_="cmp-id")
245+
* bool("L" in label_style)),
221246
" " * (bool(data) and "L" in label_style),
222247
data * bool("V" in label_style),
223248
x=point.real,
@@ -238,9 +263,7 @@ def make_text_point(t1: complex, t2: complex, **options) -> complex:
238263
return text_pt
239264

240265

241-
def make_plus(
242-
terminals: list[Terminal], center: complex, theta: float, **options
243-
) -> str:
266+
def make_plus(terminals: list[Terminal], center: complex, theta: float, **options) -> str:
244267
"Make a + sign if the terminals indicate the component is polarized."
245268
if all(t.flag != "+" for t in terminals):
246269
return ""
@@ -268,9 +291,7 @@ def arrow_points(p1: complex, p2: complex) -> list[tuple[complex, complex]]:
268291
]
269292

270293

271-
def make_variable(
272-
center: complex, theta: float, is_variable: bool = True, **options
273-
) -> str:
294+
def make_variable(center: complex, theta: float, is_variable: bool = True, **options) -> str:
274295
"Draw a 'variable' arrow across the component."
275296
if not is_variable:
276297
return ""
@@ -319,9 +340,7 @@ def is_clockwise(terminals: list[Terminal]) -> bool:
319340
return False
320341

321342

322-
def sort_for_flags(
323-
terminals: list[Terminal], box: Cbox, *flags: list[str]
324-
) -> list[Terminal]:
343+
def sort_for_flags(terminals: list[Terminal], box: Cbox, *flags: list[str]) -> list[Terminal]:
325344
"""Sorts out the terminals in the specified order using the flags.
326345
Raises and error if the flags are absent."""
327346
out = ()

schemascii/wires.py

+3-15
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
from cmath import phase, rect
22
from math import pi
33
from .grid import Grid
4-
from .utils import iterate_line, merge_colinear, XML, find_dots
4+
from .utils import iterate_line, bunch_o_lines, XML, find_dots
55

66
# cSpell:ignore dydx
77

88
DIRECTIONS = [1, -1, 1j, -1j]
99

1010

11-
def next_in_dir(
12-
grid: Grid, point: complex, dydx: complex
13-
) -> tuple[complex, complex] | None:
11+
def next_in_dir(grid: Grid, point: complex, dydx: complex) -> tuple[complex, complex] | None:
1412
"""Follows the wire starting at the point in the specified direction,
1513
until some interesting change (a corner, junction, or end). Returns the
1614
tuple (new, old)."""
@@ -115,17 +113,7 @@ def next_wire(grid: Grid, **options) -> str | None:
115113
raise RuntimeError("0-length wire")
116114
dots = find_dots(line_pieces)
117115
return XML.g(
118-
*(
119-
XML.line(
120-
x1=p1.real * scale,
121-
y1=p1.imag * scale,
122-
x2=p2.real * scale,
123-
y2=p2.imag * scale,
124-
stroke__width=stroke_width,
125-
stroke=color,
126-
)
127-
for p1, p2 in line_pieces
128-
),
116+
bunch_o_lines(line_pieces, **options),
129117
*(
130118
XML.circle(
131119
cx=pt.real * scale,

0 commit comments

Comments
 (0)