@@ -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
7593def 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
99118def 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:
126146del 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 = ()
0 commit comments