# Last edited on 2021-02-17 02:06:34 by jstolfi def add_contact(oph, ct): ''' Appends the contact {ct} (which should be an object of class {Contact}) to the list of contacts of path {ph}. Assumes that the fields {ct.ph[i],ct.ix[i],ct.tcov[i]} are defined, and {ct.ph[i] = ph} for {i=0} or {i=1}, where {ph} is the {Path} object underlying {oph}. The orientation of {oph} is irrelevant. ''' path_IMP.add_contact(oph, ct) def contacts(oph): ''' Returns a list of the contacts of the {Path} object underlying the oriented path {oph}, in arbitrary order. The orientation of {oph} is irrelevant. ''' def append_element(ogr, oph, ocn0, ocn1): ''' Adds the oriented path {oph} as a new element at the end of the oriented group {ogr}, considering its orientation. Namely, if the group had {n} elements, after this operation {elem(ogr,n)} will be {oph}. Also adds the two connectors {ocn0} and {ocn1} to the link lists of {ogr}. If {ophant = elem(ogr,n-1)}, then the connector {ocn0} must go from {path.pfin(ophant)} to {path.pini(oph)}, and {ocn1} must go from {path.pini(ophant)} to {path.pfin(oph)}. ''' group_IMP.append_element(ogr, oph, ocn0, ocn1) def segment_clip_by_circle(p, q, ctr, R): # Let {r(t)} be {t*p + (1-t)*q} for {t} in {[0 _ 1]} . Determine {t0} # and {t1} so that {r(t)} is inside or on the boundary of {C} if {t} # is in {[t0 _ t1]}, and outside otherwise: if R <= 0: # Circle has no interior, all outside: t0 = 0; t1 = 0 else: # Determine the coefficients of the quadratic formula # {A t^2 + B t + C} for the characteristic function # {|r(t) - ctr|^2 - R^2} where {r(t) = : px = p[0] - ctr[0]; vx = q[0] - p[0] py = p[1] - ctr[1]; vy = q[1] - p[1] A = vx*vx + vy*vy B = 2*(vx*px + vy*py) C = px*px + py*py - R*R assert A >= 0 if A < 1.0e-8: # Segment is too short, replace by midpoint: if A/4 + B/2 + C <= 0: # Midpoint is inside, assume all inside: t0 = 0; t1 = 1 else: # Midpoint is outside, assume all outside: t0 = 0; t1 = 0 else: Delta = B*B - 4*A*C if Delta <= 0: # Zero or one root -- the line of the segment does not enter the circle: t0 = 0; t1 = 0 else: sD = sqrt(Delta) t0 = (-B - sD)/(2*A) t1 = (-B + sD)/(2*A) assert t0 <= t1 # Now pick the non-empty parts: Lin = []; Lot = [] if t0 >= 1 or t1 <= 0 or t0 >= t1: # Whole segment is outside: Lot.append((p,q)) elif t0 <= 0 and t1 >= 1: # Whole segment is inside: Lin.append((p,q)) elif t0 <= 0 and 0 < t1 and t1 < 1: # Inside from 0 to {t1}, then outside: r1 = rn.mix(t1, p, 1-t1, q) Lin.append((p,r1)) Lot.append((r1,q)) elif 0 < t0 and t0 < 1 and t1 >= 1: # Outside from 0 to {t0}, rest inside: r0 = rn.mix(t0, p, 1-t0, q) Lot.append((p,r0)) Lin.append((r0,q)) else: assert 0 < t0 and t0 < t1 and t1 < 1 # Inside between {t0} and {t1}, rest outside: r0 = rn.mix(t0, p, 1-t0, q) r1 = rn.mix(t1, p, 1-t1, q) Lot.append((p,r0)) Lin.append((r0,r1)) Lot.append((r1,q)) return Lin, Lot def circle_line_meet(ctr, R, d, ang): if R < 0: return None if abs(d) > R: return None else: h = sqrt(max(0, R*R - d*d)) c = cos(ang); s = sin(ang) m = rn.add(ctr, (-d*s, +d*c)) u = (+h*c, +h*s) p = rn.sub(m, u) q = rn.add(m, u) return (p, q) def ring_line_meet(ctr, Rin, Rot, d, ang): if Rot < 0 or Rot < Rin: return () if abs(d) > Rot: return () Sot = circle_line_meet(ctr, Rot, d, ang) if Sot == None: return () Sin = circle_line_meet(ctr, Rin, d, ang) if Sin == None: return (Sot,) else: Slo = (Sot[0],Sin[0]) Shi = (Sin[1],Sot[1]) return (Slo,Shi) ''' Two shapes are said to be /equivalent/ if {Int(F)} and {Int(G)} have the same closure, and {Ext(F)} and {Ext(G)} have the same closure. Note that there may be infinitely may shapes equivalent to the vacuum or to the plenum. ??? Is this concept ecessary? ??? ''' ''' For example, let {P0,P1,P2,P3} be four raster lines in consecutive scan-lines, each oriented left to right and making significant contact with the previous and following ones. Suppose that we have decided that they should be extruded in succession, in alternating directions. We can join them into a single path in four different ways, depending on whether we start with {P0}, {rev(P0)}, {P3}, or {rev(P3)}. The first choice requires two connectors along the right edge and one on the left edge. The second choice requres the different connectors{od} field may be 0 to mean that they should be concatenated in that order, and 1 to mean that they be executed in the opposite order (but still each in the same direction). The {dr} bit then tells whether the resulting path should be used as is, or entirely reversed. That is, the four possibilities mean: dr od execution order initial and final points -- -- ---------------- ------------------------- 0 0 P0,P1,P2,P3 pini(P0),pfin(P3) 0 1 P3,P2,P1,P0 pini(P3),pfin(P0) 1 0 rev(P3),rev(P2),rev(P1),rev(P0) pfin(P3),pini(P0) 1 1 rev(P0),rev(P1),rev(P2),rev(P3) pfin(P0),pini(P3) As implied by this example, the paths in a block do not need to form a single continuous path by themselves. Therefore, besides the {m} elementary paths, each block also has one or more lists of {m-1} /connectors/ that bridge the endpoints of suceessive elements. Depending on the order and direction of execution, one of these lists of connectors must be intercalated with the elementary paths, as given or reversed, to form a single continuous path. In the above example, to form a single continuous path, one set of connectors must be used for the 00 orientation, and another one for the 01 orientation. These are returned by the function {conn(obc,k)} for {k=0,1,..m-2}. In the example, if {obc = (bc,0,0)}, then {conn(obc,0)} is a path that connect {pfin(P1)} to {pini(P2)}. If {obc} is {(bc,0,1)}, then {conn(obc,0)} will instead connect {fin(P3)} to {pini(P2)}. And so on. ''' def conn(obc, ch): ''' Returns the oriented path {ocn} that is the connector between element {oel0=choice(obc,ch)} and {oel1=choice(obc,ch+1)}, oriented according to the execution order. That is, {path.pini(ocn)} is {path.pfin(oel0)} and {path.pfin(ocn)} is {path.pini(oel1)}. The index {ch} must be in {0..nel-2} where {nel=nchoices(obc)}. ''' return block_IMP.conn(obc, ch) def pini(obc): ''' Returns the initial point of the oriented block {obc}, as it would be executed. ''' return block_IMP.pini(obc) def pfin(obc): ''' Returns the final point of the oriented block {obc}, as it would be executed. ''' return block_IMP.pfin(obc) def join(obc): ''' Returns a single tool-path that is the result of concatenating the elements of the oriented block {obc}, in the specified order and direction, intercalated with the appropriate list of connectors. The block {obc} is not affected. ''' return block_IMP.join(obc) def rev(obc): ''' Returns an oriented block {obc'} that has the same elements of {obc}, but in the opposite order and each with the opposite direction. That is, {join(rev(obc))} is the same sequence of moves as {path.rev(join(obc))}. Note that this operation reverses the order and direction of the connectors of {obc}, but does not change the set of connectors. Thus the total execution time {T=extime(obc)} is preserved, and the cover times of contacts relative to {join(obc)} are completmented relative to {T}. ''' return block_IMP.rev(obc) # TIMING def extime(obc): ''' Returns the total execution time of the block {obc}, considering its orientation, including the appropriate set of connectors. Same as {path.extime(join(obc))}, but does not actually build the path. ''' return block_IMP.extime(obc) def tini(obc, ch): ''' Returns the time at which the execution of element {choice(obc,ch)} would begin, counted since the beginning of the block's execution. Considers the block's orientation, and includes the time of interevening connectors from the appropriate set. For convenience, if {ch} is the number of block elements {nel}, returns {extime(obc)}. ''' return block_IMP.tini(obc, ch) def tfin(obc, ch): ''' Returns the time at which execution of element {choice(obc,ch)} would be complete, counted since the beginning of the block's execution. Considers the block's orientation, and includes the time of interevening connectors from the appropriate set. For convenience, if {k} is {-1}, returns zero. ''' return block_IMP.tfin(obc,k) # PLOTTING def plot(c, obc, dp, wblock, cblock, wconn, cconn, wtrace, ctrace, wjump, cjump, axes, dots, arrows): ''' Plots the oriented block {obc} on the {pyx} canvas {c}. The plot will be displaced by the vector {dp} (a 2-tuple of floats). The stroke width {wblock} and the color {cblock} are used to paint a background behind all traces in the elements and both connector sets, to show the region potentially covered by the block. The width {wconn} and the color {cconn} are used to paint the fat traces of the set of connectors that is determined by the orientation of {obc}. The width {wtrace} and the color {ctrace} are used to show the fat traces in the elements of {obc} (not the connectors). The width {wjump} is used to draw all jumps in the elements (with color {cjump}) and in the selected connectors set (with color {cconn}), as dashed lines. Any of these parts can be suppressed by giving {None} or 0 as the width. If {axes} is true, the axes of the traces in the elements are drawn with width {wjump} and a darkened version of the respective trace color. If {arrows} is true, arrowheads will be drawn on all jumps and trace axes (even if the axes are not drawn). ''' block_IMP.plot(c, obc, dp, wblock, cblock, wconn, cconn, wtrace, ctrace, wjump, cjump, axes, dots, arrows) def make_test_raster(R, Ymin, Ymax, strace, side, parms): ''' Makes a test block consisting of a set of filling elements in the space between two concentric circles of radius {R} and {0.5*R} with center at the point {(R+2)*(1,1)}. The {Y} ordinates of the trace axes will be in the interval {[Ymin _ Ymax]} and will be spaced {strace} apart. If the filling elements are interrupted by the inner circle, chooses the left part if {side} is negative, and the right part if {side} is positive. ??? Should use {parms['road_width']} instead of {strace} ??? ''' return block_IMP.make_test_raster(R, Ymin, Ymax, side, strace, parms) def __init__(self, ...): self.cumtex_el = cumtex_el self.eord = eord self.ocn = ocn self.cumtex_cn = cumtex_cn def make_trivial(oph): tex = path.extime(oph) return block.Block\ ( oel = ( oph, ), cumtex_el = ( tex, ), eord = ( ( +1, ), ( -1, ), ), ocn = ( (), () ) cumtex_cn = ( (), () ) ) # TIMING def extime(obc): bc, ch = unor(obc) nch = len(bc.ochs) # Bits {dr} and {ch} determine which connector set to use: if dr == ch: i = 0 # The native orientation connector set. else: i = 1 # The alternative connector set. return bc.cumtex_el[nch-1] + (0 if nch == 1 else bc.cumtex_cn[i][nch-2]) def tini(obc, k): bc, ch = unor(obc) nch = len(bc.ochs) assert k >= 0 and k <= nch if k == 0: return 0 if k == nch: return extime(obc) # Bits {dr} and {ch} determine which connector set to use: if dr == ch: i = 0 # The native orientation connector set. else: i = 1 # The alternative connector set. # Bit {ch} determines the choice of elements and connectors: if ch == 0: # Native choice: return bc.cumtex_el[k-1] + bc.cumtex_cn[i][k-1] else: # Reversed choice assert nch >= 2 cumtex_el = bc.cumtex_el[nch-1] - bc.cumtex_el[nch-1-k] cumtex_cn = bc.cumtex_cn[i][nch-2] - (0 if k == nch-1 else bc.cumtex_cn[i][nch-2-k]) return cumtex_el + cumtex_cn def tfin(obc, k): bc, ch = unor(obc) nch = len(bc.ochs) assert k >= -1 and k <= nch-1 if k == -1: return 0 return tini(obc, k) + path.extime(choice(obc, k)) # PLOTTING def plot(c, obc, dp, wblock, cblock, wconn, cconn, wtrace, ctrace, wjump, cjump, axes, arrows): bc, ch = unor(obc) nch = len(bc.ochs) # Plot the block background where there are traces (ncluding both connectors): for k in range(nch): ophk = choice(obc,k) path.plot(c, ophk, dp, jmp=False, clr=cblock, waxes=wblock, dashed=False, wdots=0, szarrows=0) if k < nch-1: ocnk0 = conn(obc,k) ocnk1 = conn(rev(obc),k) path.plot(c, ocnk0, dp, jmp=False, clr=cblock, waxes=wblock, dashed=False, wdots=0, szarrows=0) path.plot(c, ocnk1, jmp=False, dp, clr=cblock, waxes=wblock, dashed=False, wdots=0, szarrows=0) # Plot the nominal areas of traces - first the proper connectors, then elements: for k in range(nch-1): ocnk = conn(obc,k) path.plot(c, ocnk, dp, jmp=False, clr=cconn, waxes=wconn, dashed=False, wdots=0, szarrows=0) for k in range(nch): ophk = choice(obc,k) path.plot(c, ophk, dp, jmp=False, clr=ctrace, waxes=wtrace, dashed=False, wdots=0, szarrows=0) # Plot trace axes and/or dots and/or arrows: trc_waxis = wjump if axes else 0 trc_wdots = 2*wjump if dots else 0 trc_szarrows = szarrows if arrows else 0 for k in range(nch-1): ocnk = conn(obc,k) path.plot(c, ocnk, dp, jmp=False, clr=caxis, waxes=trc_waxis, dashed=False, wdots=0, szarrows=0) for k in range(nch): ophk = choice(obc,k) path.plot(c, ophk, dp, jmp=False, clr=caxis, waxes=trc_waxis, dashed=False, wdots=trc_wdots, szarrows=trc_szarrows) # Plot jumps: jmp_waxis = wjump jmp_wdots = 2*wjump for k in range(nch-1): ocnk = conn(obc,k) path.plot(c, ocnk, dp, jmp=True, clr=cjump, waxes=wjump, dashed=True, wdots=jmp_wdots, szarrows=True) for k in range(nch): ophk = choice(obc,k) path.plot(c, ophk, dp, jmp=True, clr=cjump, waxes=wjump, dashed=True, wdots=jmp_wdots, szarrows=True) # DEBUGGING pant = pini(obc) # Final point of previous path. tant = 0 # Cumulative execution time. for k in range(nch): # --- validate the element {k} --------------------- oelk = choice(obc, k) # sys.stderr.write("oelk = %s\n" % str(oelk)) elk, drk = path.unor(oelk) # Just to typecheck. # sys.stderr.write("elk = %s drk = %s\n" % (str(elk), str(drk))) el_pini = path.pini(oelk) el_pfin = path.pfin(oelk) # sys.stderr.write("pant = %s pini = %s\n" % (str(pant), str(el_pini))) assert pant == el_pini el_tini = tini(obc, k) el_tfin = tfin(obc, k) el_tex = path.extime(oelk) # sys.stderr.write("\n") # sys.stderr.write("k = %d tant = %12.8f el_tini = %12.8f el_tfin = %12.8f el_tex = %12.8f\n" % (k,tant,el_tini,el_tfin,el_tex)) assert abs(tant - el_tini) < 1.0e-8 assert abs((el_tfin - el_tini) - el_tex) < 1.0e-8 pant = el_pfin tant = el_tfin if k < nch-1: # --- validate the connector after that element: ocnk = conn(obc, k) # sys.stderr.write("ocnk = %s\n" % str(ocnk)) cnk, drk = path.unor(ocnk) # Just to typecheck. # sys.stderr.write("cnk = %s drk = %s\n" % (str(cnk), str(drk))) cn_pini = path.pini(ocnk) cn_pfin = path.pfin(ocnk) # sys.stderr.write("pant = %s pini = %s\n" % (str(pant), str(cn_pini))) assert pant == cn_pini cn_tex = path.extime(ocnk) # sys.stderr.write("k = %d cn_tex = %12.8f\n" % (k,cn_tex)) pant = cn_pfin tant = tant + cn_tex assert pant == pfin(obc) assert abs(tant - extime(obc)) < 1.0e-8 def make_test_raster(R, Ymin, Ymax, strace, side, parms): Rot = R - strace/2 Rin = 0.5*R + strace/2 ctr = (R+2, R+2) nch = int(ceil(R/strace)) oels = [] ocn0s = [] ocn1s = [] oelant = None for k in range(2*nch+1): Y = (k-nch)*strace if Ymin <= Y and Y <= Ymax: elk = make_test_raster_path(Rot, Rin, ctr, Y, side, parms) oelk = elk if (k % 2) == 0 else path.rev(elk) oels.append(oelk) if oelant != None: cn0k = make_test_raster_connector(oelant, oelk, strace, parms) cn1k = make_test_raster_connector(path.rev(oelant), path.rev(oelk), strace, parms) ocn0s.append(cn0k) ocn1s.append(cn1k) oelant = oelk bc = from_paths(oels, ocn0s, ocn1s) return bc def make_test_raster_path(Rot, Rin, ctr, Y, side, parms): ''' Returns a path that is a single raster line inside the annulus, at the given {Y} ordinate relative to the center {ctr}. If the raster is interrupted, uses the part on the specified {side}. ''' Xot = sqrt(max(0, Rot*Rot - Y*Y)) if abs(Y) > Rin: # Single trace: Xmin = -Xot Xmax = +Xot else: # Interrupted trace: Xin = sqrt(max(0, Rin*Rin - Y*Y)) if side < 0: Xmin = -Xot Xmax = -Xin else: Xmin = +Xin Xmax = +Xot # Create the single-trace element: p0 = rn.add(ctr, (Xmin,Y)) p1 = rn.add(ctr, (Xmax,Y)) ph = path.from_points((p0, p1), parms) return ph def circle_raster(ctr, R, d, ang, parms): ''' Let {L} be the straight line that makes an angle {ang} with the {X}-axis and passes at distance {d} from the point {ctr}. Returns a path {P} consisting of only one trace along {L} that lies inside the circle {C} with center {ctr} and radius {R}. The raster line is assumed to have width {strace = parms['solid_raster_width']}. The trace's end caps will just touch the circle's boundary. That is, the axis of the trace will be the part of {L} inside the circle with radius {R-strace/2}. Note that the radius {R} must be adjusted by the caller to account for the condour's trace width. If no such trace is possible (in particular, if {R} is negative or less than {strace/2}), returns {None}. Takes the sign of {d} into account. The trace is oriented left to right relative to the {XY} coord system rotated by {ang}. . ''' return example_path_IMP.circle_line_trace(ctr, R, d, ang, parms) def circle_raster(ctr, R, d, ang, parms): strace = parms['solid_raster_width'] ctrace = parms['contour_trace_width'] # Find the scan line intersections with the circle, # accounting for the raster line's round caps: S = hacks.circle_line_meet(ctr, R - strace/2, d, ang) if S == None: return None ph = path.from_move(S[0], S[1], False, parms) return ph def test_cover_time(): sys.stderr.write("--- testing {cover_time} -----------------\n") wdfill = parms['solid_raster_width'] # Make an incomplete a square frame: Rot = 3 ctr2 = (3*Rot + 3, Rot + 1) q1 = rn.add(ctr2, (+Rot, -Rot)) q2 = rn.add(ctr2, (+Rot, +Rot)) q3 = rn.add(ctr2, (-Rot, +Rot)) q4 = rn.add(ctr2, (-Rot, -Rot)) q5 = rn.add(ctr2, ( 0, -Rot)) phb = path.from_points([q1, q2, q3, q4, q5], wdfill, parms) # Make a single move next to the top of the square: u2 = rn.add(ctr2, (-Rot+wdfill, +Rot-wdfill)) u3 = rn.add(ctr2, (+Rot-wdfill, +Rot-wdfill)) phc = path.from_move(u2, u3, wdfill, parms) # Make a contact between paths {phb} and {phc}: v20 = rn.add(ctr2, (00.0, +Rot-wdfill/2)) # Start of contact. v21 = rn.add(ctr2, (+2.0, +Rot-wdfill/2)) # End of contact. v2m = rn.mix(0.5, v20, 0.5, v21) # Midpoint of contact. c = pyx.canvas.canvas() pyx.unit.set(uscale=2.0, wscale=2.0, vscale=2.0) ctraces2 = pyx.color.rgb.green ctraces3 = pyx.color.rgb.red ccont = pyx.color.rgb.blue waxes = 0.05*wdfill; # Axes of traces. wcont = 0.15*wdfill; axes = True dots = True arrows = True matter = False path.plot_standard(c, phb, (0,0), ctraces2, waxes, axes, dots, arrows, matter) path.plot_standard(c, phc, (0,0), ctraces3, waxes, axes, dots, arrows, matter) sty = [pyx.style.linewidth(wcont), pyx.style.linecap.round, ccont] c.stroke(pyx.path.line(v2m[0], v2m[1], v2m[0], v2m[1]), sty) hacks.write_plot(c, "tests/out/path_TST") # Check total path times {phb}: assert path.nelems(phb) == 4 extime2a = 0 tex20 = move.extime(path.elem(phb,0)); extime2a += tex20 tex21 = move.extime(path.elem(phb,1)); extime2a += tex21 tex22 = move.extime(path.elem(phb,2)); extime2a += tex22 tex23 = move.extime(path.elem(phb,3)); extime2a += tex23 extime2b = path.extime(phb) # sys.stderr.write("move times = %s\n" % str((tex20,tex21,tex22,tex23))) # sys.stderr.write("extime2a = %12.8f extime2b = %12.8f\n" % (extime2a,extime2b)) assert abs(extime2a-extime2b) <= 1.0e-8 dq23 = rn.dist(q2, q3) rmq23 = abs(v2m[0] - q2[0])/abs(q3[0] - q2[0]) # Rel pos of contact on {mvq23} # Contact cover tims on paths {phb} and {rev(phb)}, computed "by hand": tcov2a = \ move.extime(path.elem(phb,0)) + \ move.nozzle_travel_time(dq23, False, rmq23*dq23, parms) tcov2c = \ move.extime(path.elem(phb,3)) + \ move.extime(path.elem(phb,2)) + \ move.nozzle_travel_time(dq23, False, (1-rmq23)*dq23, parms) # sys.stderr.write("tcov2a = %12.8f tcov2c = %12.8f sum = %12.8f extime2b = %12.8f\n" % (tcov2a, tcov2c, tcov2a+tcov2c,extime2b)) assert abs(tcov2a+tcov2c-extime2b) <= 1.0e-8 # Contact cover time on paths {phb} and {rev(phb)}, computed by {path.cover_time}: tcov2b = path.cover_time(phb, 1, v2m, parms) tcov2d = path.cover_time(path.rev(phb), 2, v2m, parms) # sys.stderr.write("tcov2b = %12.8f tcov2d = %12.8f sum = %12.8f extime2b = %12.8f\n" % (tcov2b, tcov2d, tcov2b+tcov2d, extime2b)) assert abs(tcov2b+tcov2d-extime2b) < 1.e-8 assert abs(tcov2a - tcov2b) <= 1.0e-8 assert abs(tcov2c - tcov2d) <= 1.0e-8 def colors_rgb(): colors = [ pyx.color.rgb( 1.000, 0.000, 0.400 ), pyx.color.rgb( 0.000, 0.400, 1.000 ), pyx.color.rgb( 0.200, 0.800, 0.000 ), pyx.color.rgb( 0.702, 0.000, 0.702 ), pyx.color.rgb( 1.000, 0.392, 0.000 ), pyx.color.rgb( 0.329, 0.443, 0.596 ), pyx.color.rgb( 0.894, 0.000, 0.000 ), pyx.color.rgb( 0.459, 0.698, 0.439 ), pyx.color.rgb( 0.773, 0.239, 0.239 ), pyx.color.rgb( 0.000, 0.761, 0.729 ), pyx.color.rgb( 0.973, 0.761, 0.376 ), pyx.color.rgb( 0.000, 0.529, 0.267 ), pyx.color.rgb( 0.973, 0.329, 0.475 ), pyx.color.rgb( 0.000, 0.549, 0.651 ), pyx.color.rgb( 0.957, 0.498, 0.482 ), pyx.color.rgb( 0.616, 0.839, 0.306 ), pyx.color.rgb( 0.588, 0.373, 0.678 ), pyx.color.rgb( 0.212, 0.671, 0.710 ), pyx.color.rgb( 0.996, 0.369, 0.318 ), pyx.color.rgb( 0.361, 0.722, 0.361 ), pyx.color.rgb( 0.620, 0.239, 0.392 ), pyx.color.rgb( 0.000, 0.682, 0.859 ), pyx.color.rgb( 0.745, 0.161, 0.925 ), pyx.color.rgb( 0.000, 0.694, 0.349 ), ] return colors def colors_tex(): ''' Returns a list of color names for {TeX} figures. ''' colors = [ 'magenta', 'cyan', 'orange', 'green', 'teal', 'purple', 'pink', 'gray', 'lime' ] return colors def circle_parms(R, wd, nt): ''' Returns four parameters for a {circle} path: the angular increment {astep} between successive endpoints, the angular gap {agap} between the starting and ending points, and the radii {Rin,Rot} of the inscribed and circumscribed circles. ''' def circle_parms(R, wd, nt): agap = wd/2/R # Angular size of gap between the endpoints. astep = 2*pint Rin = R*cos(astep/2) - wd/2 Rot = R + wd/2 return astep, agap, Rin, Rot # Compute the bbox {B} of all blocks: B = None for bc in BS: Bbc = block.bbox(bc) B = Bbc if B == None else rn.box_join(B, Bbc) assert B != None # Draw a grid covering it: plo,phi = B plog = (min(0,floor(plo[0])-1), min(0,floor(plo[1])-1)) xszg = ceil(phi[0] - plog[0])+1 yszg = ceil(phi[1] - plog[1])+1 wgrid = 0.5*waxes hacks.plot_grid(c, None, wgrid, plog, xszg, yszg, 3*wgrid, 1,1) # Split the choices of block {bc} into two synced lists {phs0,phs1}: original paths and reversals: ophs0 = []; ophs1 = [] has_rev = False # True if {bc} has both a path and its reversal. for ip in range(np): ophi = block.choice(bc, ip) phi, dri = path.unpack(ophi) new = True # True if choice {ophi} is not the reversal of a previous choice. for jp in range(len(ophs0)): ophj = ophs0[j] phj, drj = path.unpack(ophj) if phi == phj: assert dri != drj # Choices should be distinct paths. assert new and ophs1[jp] == None # Can't have more than 2 reversals ophs1[jp] = ophi has_rev = True new = False if new: ophs0.append(phi); ophs1.append(None) npd = len(ophs0) assert len(ophs1) == npd