#! /usr/bin/python3 # Implementation of module {paper_figures_B} # Last edited on 2021-10-02 14:51:33 by stolfi import paper_figures_B import move import move_example import move_parms import contact import path import path_example import rootray_shape import rootray import raster import path_hp import block import hacks import pyx import rn import sys from math import sqrt, hypot, sin, cos, atan2, floor, ceil, inf, nan, pi def compute_filet_center(xm,ym, xn,yn, ri, ro): # Computes the coords {xt,yt} of the center of a circle of radius # {ri} that is tangent to the circles of radius {ro} # with centers {pm=(xm,ym)} and {pn=(xn,yn)}. Returns the solution # that lies to right of the line from {pm} to {pn}. # rio = ri + ro dmn = rn.dist((xm,ym), (xn,yn)) xu = (yn - ym)/dmn yu = (xm - xn)/dmn xo = (xm + xn)/2 yo = (ym + yn)/2 h = sqrt(max(0, rio*rio - dmn*dmn/4)) assert h > 0, "can't compute filet" xt = xo + h*xu yt = yo + h*yu return xt, yt # ---------------------------------------------------------------------- def key_coords(dil): # The raster line endpoints lie on the boundary of a region {D}. # Returns the key coordinates and radii of the {D} region, dilated by {dil}. # Assumes that the raster spacing {wdf} is 1. # Dimensions and coordinates of undilated {D}: dxms = 5.00 # X distance between centers of holes 'm' and 's'. dxgi = 3.00 # X distance between centers of slot and right shoulders. dyrm = 2.00 # Y distance between centers of slot bottom and hole 'm'. wdtb = 8.00 # Width of right tab, slot center to shoulder center. ygap = 0.10 # Extra gap between top/bot scanline and top/bot edges of {D} rado = 4.500 + ygap # Radius of hole rims and shoulders. radi = 2.499 # Radius of holes and half-width of notch and slot. radt = radi # Radius of fillet between head and neck. # Dimensions and parameters of dilated {D}: ya = 1.00 - ygap - dil # Y of bottom edge of dilated {D}. yz = 18.00 + ygap + dil # Y of top edge of dilated {D} ri = radi - dil # Inner radius of holes. assert ri > 0, "hole radius is too small" ro = rado + dil # Outer radius of hole rims and smooth shoulders. rt = radt - dil # Radius of neck-head fillets yr = ya + ro # Y of centers of slot/notch bottom arcs. ym = yr + dyrm # Y of center of hole 'm'. ys = yz - ro # Y of center of hole 's'. xm = ceil(radi + 3) # X of center of hole 'm'. xs = xm + dxms # X of center of hole 's'. xc = xs + ro # X of left edge of notch. xn = xc + ri # X of midline of notch. # Compute the center {(xt,yt)} of the # fillet of radius {rt} that between the 'head' and the 'neck' xt, yt = compute_filet_center(xm,ym, xn,yr, rt, ro) xf = xn + ri # X of right edge of notch. xg = xf + ro # X of midline of slot. xx = xg - ri # X of left edge of slot. xz = xg + ri # X of right edge of slot. xi = xg + wdtb # X of center of right shoulders. xj = xi + ro # X of right edge of part. # Sine and co-sine of angles of left perimeter dms = rn.dist((xm,ym), (xs,ys)) # Dist between centers of hole 'm' and hole 's'. cms = (xs - xm)/dms; sms = (ys - ym)/dms # Cos and sin of CW tilt of upper straight line from horz. dmt = rn.dist((xm,ym), (xt,yt)) # Dist between centers of hole 'm' and filet 't'. cmt = (ym - yt)/dmt; smt = (xt - xm)/dmt # Cos and sin of CCW incl of 'm'-'t' from vert. dnt = rn.dist((xn,yr), (xt,yt)) # Dist between centers of filet 't' and bot 'n' of notch. cnt = (xn - xt)/dnt; snt = (yr - yt)/dnt # Cos and sin of CCW tilt of 'n'-'t' from horz. return xm,xs,xt,xc,xn,xf,xg,xi,xj, ya,yr,ym,ys,yt,yz, ri,ro,rt, cms,sms,cmt,smt,cnt,snt # ---------------------------------------------------------------------- def circle_x(y, yctr, rad): # positive abscissa of point on circle with center {(0,yctr)} and radius {rad} # with ordnate {y}. dy = yctr - y x = sqrt(max(0, rad*rad - dy*dy)) return x # ---------------------------------------------------------------------- def trace_circle(xc,yc,rd, a0,a1,step, ylo,yhi): # Returns points on the circle with center {(xc,yc)} and radius {rd}, # sampled between angles {a0} and {a1} in increments of arc length # {step}, clippled to the Y range {[ylo _ yhi]}. The tracing is CCW if # {step} is positive, CW if negative. PTS = [] na = ceil(rd*(a1-a0)/step) # Number of steps. if na > 0: da = (a1 - a0)/na # Angle increment for each step. for ia in range(na+1): a = a0 + da*ia x = xc + rd*cos(a) y = yc + rd*sin(a) if y >= ylo and y <= yhi: PTS.append((x,y)) return PTS # ---------------------------------------------------------------------- def contour_points(dil, step): # The ponts are on the boundary of the filling region {D} expanded by {dil}. # Points on curve sections will be spaced by {step}. # # The result is a list {PTCS} of lists. Each element of {PTCS} is a # list of points that describe a connected component of the contour. # Each component has the last point repeating the first point. xm,xs,xt,xc,xn,xf,xg,xi,xj, ya,yr,ym,ys,yt,yz, ri,ro,rt, cms,sms,cmt,smt,cnt,snt = key_coords(dil) ams = atan2(sms,cms) # CW tilt angle of top straight seg on left side from horz. amt = atan2(smt,cmt) # CCW incl angle of 'm'-'t' line from vert. ant = atan2(snt,cnt) # CCW tilt angle of 'n'-'t' line from horz. # .................................................................... # Outer perimeter: PTOS = [] yeps = 0.05 # Min Y space between arc points and scan-lines. # Bottom left edge: PTOS.append((xn, ya)) PTOS.append((xi, ya)) # Bottom right shoulder: PTS = trace_circle(xi,yr,ro, -0.5*pi,00.0*pi,+step, ya+yeps, +inf) PTOS += PTS # Top right shoulder: PTS = trace_circle(xi,ys,ro, 00.0*pi,+0.5*pi,+step, -inf, yz-yeps) PTOS += PTS # Top right edge: PTOS.append((xi, yz)) PTOS.append((xg, yz)) # Top right shoulder of notch: PTS = trace_circle(xg,ys,ro, +0.5*pi,+1.0*pi,+step, -inf, yz-yeps) PTOS += PTS # Bottom semicircle of notch: PTS = trace_circle(xn,yr,ri, 00.0*pi,-1.0*pi,-step, -inf, +inf) PTOS += PTS # Rim of hole 's': PTS = trace_circle(xs,ys,ro, 00.0*pi,(0.5*pi+ams),+step, -inf, +inf) PTOS += PTS # Rim of hole in hole 'm': PTS = trace_circle(xm,ym,ro, (0.5*pi+ams),(1.5*pi+amt),+step, -inf, +inf) PTOS += PTS # Rim of filet 't': PTS = trace_circle(xt,yt,rt, (0.5*pi+amt),(0.0*pi+ant),-step, -inf, +inf) PTOS += PTS # Rim of bootom left shoulder: PTS = trace_circle(xn,yr,ro, (-1.0*pi+ant),-0.5*pi,+step, ya+yeps, +inf) PTOS += PTS # Close curve: PTOS.append(PTOS[0]) # .................................................................... # .................................................................... # Hole in hole col 1: PTH1S = trace_circle(xm,ym,ri, +2.0*pi,00.0*pi,-step, -inf, +inf) # Fix possible roundoff errors in last point: if PTH1S[0] != PTH1S[-1]: PTH1S[-1] = PTH1S[0] # .................................................................... # .................................................................... # Top hole in hole col 2: PTH2S = trace_circle(xs,ys,ri, +2.0*pi,00.0*pi,-step, -inf, +inf) # Fix possible roundoff errors in last point: if PTH2S[0] != PTH2S[-1]: PTH2S[-1] = PTH2S[0] # .................................................................... # .................................................................... # Slot: PTSS = [] # Bottom half-circle of slot: PTS = trace_circle(xg,yr,ri, 00.0*pi,-1.0*pi,-step, -inf, +inf) PTSS += PTS # Top semicircle of slot: PTS = trace_circle(xg,ys,ri, +1.0*pi,00.0*pi,-step, -inf, +inf) PTSS += PTS PTSS.append(PTSS[0]) # .................................................................... PTCS = [ PTOS, PTH1S, PTH2S, PTSS ] # Remove close points and rotate 180: xctr = (xm - ro + xj)/2 yctr = (ya + yz)/2 dmin = 1.0e-3 for i in range(len(PTCS)): PTS = PTCS[i] PTS = hacks.poly_cleanup(PTS, dmin) if PTS[0] != PTS[-1]: PTS.append(PTS[0]) PTS = [ (2*xctr - p[0], 2*yctr - p[1]) for p in PTS ] PTCS[i] = PTS return PTCS # ---------------------------------------------------------------------- def raster_endpoints(wdf): # Computes the endpoints of raster lines # # The result is a list {PTRS} such that {PTRS[i][j][k]} is endpoint # {k} (0=left, 1=right) of raster {j} on scan-line {i} from bottom. # # Assumes that the endpoints are on the boundary of region {D}, that # the scan-line spacing is {wdf}, and that the Y coords of scan-lines # are integer multiples of {wdf}. Note that rasters extend by {wdf/2} # ouside of {D}. # Get the contours of the undilated region {D}: dil = 0 step = 0.25*wdf PTCS = contour_points(dil, step) # Get the bounding box of those points, expanded: B = None for PTS in PTCS: for p in PTS: B = rn.box_include_point(B, p) mrg = (2*wdf,2*wdf) # Extra box margin. B = rn.box_expand(B, mrg, mrg) eps = 1.0e-5 # Fudge factor to compensate roundoff errors. # Create a CSG tree for the region {D}: SH = rootray_shape.from_polygons(PTCS, +1) # Obtain the rasters by stabbing it: xdir = (1,0) ydir = (0,1) ystep = 1.00*wdf yphase = 0.00*wdf PTRS = raster.endpoints_from_shape(SH, B, xdir, ydir, ystep, yphase) return PTRS # ---------------------------------------------------------------------- def link_points(wdf): # Returns the points that comprise the link paths.] # # The result is a list {PTLS} such that {PTLS[i][j][k]} is the list of points # that define the link path of index {j} between scan-lines {i} and {i+1}. # # The ponts lie on the boundary of the filling region {D}. # xm,xs,xt,xc,xn,xf,xg,xi,xj, ya,yr,ym,ys,yt,yz, ri,ro,rt, cms,sms,cmt,smt,cnt,snt = key_coords(0) PTLS = [] PTRS = raster_endpoints(wdf) # Trace the contour of region {D} without dilation: dil = 0 step = 0.25*wdf # Step to trace circular arcs (mm). PTCS = contour_points(dil, step) nsc = len(PTRS) # Number of scan-lines. for i0 in range(nsc-1): i1 = i0 + 1 # Create list {LS} of link paths in the strip between scan-lines {i0} and {i1} LS = [] RS0 = PTRS[i0] # List of raster endpoint pairs on scan-line {i0} RS1 = PTRS[i1] # List of raster endpoint pairs on scan-line {i1} if len(RS0) != 0 and len(RS1) != 0: for CS in PTCS: LSj = link_points_in_band(CS, RS0, RS1) LS += LSj PTLS.append(LS) return PTLS # ---------------------------------------------------------------------- def link_points_in_band(CS, RS0, RS1): # Given a list {CS} of points in a connected component of the boundsry of region {D}, # and the non-empty lists {RS0,RS1} of raster endpoint pairs on two consecutive scan-lines, # returns a list {LS} of the pieces of that component that cross the strp # between those two scan-lines. Specifically, {LS[k]} is the list of points of the piece with index {k}. # Only considers pieces that completely cross the strip. # Get Y coordinates of the two scan-lines: y0 = RS0[0][0][1] y1 = RS1[0][0][1] assert rn.dist(CS[0],CS[-1]) < 1.0e-8 # Contour component must be closed. np = len(CS) # Number of points in contour component. LS = [] # Find first index {k} such that {CS[k]} is outside or on boundary of strip # and {CS[k+1]} is not on the same scan-line (indices modulo {np}): k_start = None for k in range(np): p = CS[k] q= CS[(k+1)%np] if p[1] != q[1]: if p[1] <= y0 or p[1] > y1: k_start = k; break if k_start == None: # Component is entirely inside strip (?!). return LS PS = None # Points in current link path. y_enter = None # Y of first point in {PS}, or {None} for dk in range(np): k = k_start + dk p = CS[k % np] q = CS[(k+1) % np] ps, qs = clip_seg_to_strip(p, q, y0, y1) if ps == None: assert qs == None assert PS == None # We should have closed it by hitting {y0} or {y1}. else: assert qs != None if y_enter == None: # First seg of a new link: assert ps[1] != qs[1] y_enter = ps[1] assert y_enter == y0 or y_enter == y1 PS = [ ps ] assert y_enter != None assert PS != None if qs != ps: PS.append(qs) if qs[1] == y0 or qs[1] == y1: # About to exit strip: if qs[1] != y_enter: # Exiting from opposite side of strip -- completed a link path: LS.append(PS) # Either way, we are done with the currenr link path: PS = None; y_enter = None # Since we started outside or on the boundary of the strip, we must be quiescent: assert y_enter == None assert PS == None return LS # ---------------------------------------------------------------------- def clip_seg_to_strip(p,q, y0,y1): # Clips the segment from {p} to {q} to the interior of the strip # between {y0} and {y1}, exclusive. # # Returns the endpoints {ps,qs} of the clipped segment. At most one of # them will be on the line {y0}, or on the line {y1}. Returns # {None,None} if the segment is entirely outside or on the boundary of the strip. assert y0 < y1 # Sort the points by Y: swap = False if p[1] > q[1]: q,p = p,q; swap = True assert p[1] <= q[1] if p[1] >= y1 or q[1] <= y0: return None, None if p[1] >= y0: ps = p else: assert q[1] > p[1] r = (y0 - p[1])/(q[1] - p[1]) psx = p[0] + r*(q[0] - p[0]) ps = (psx, y0) if q[1] <= y1: qs = q else: assert q[1] > p[1] r = (y1 - p[1])/(q[1] - p[1]) qsx = p[0] + r*(q[0] - p[0]) qs = (qsx, y1) if swap: return qs, ps else: return ps, qs # ---------------------------------------------------------------------- def contour(fig, mp_cont, mp_fill, mp_jump): # Returns the list {PCS} of contour paths for {paper_figures_B}. See the comments in that # function for the meaning of the parameters. wdc = move_parms.width(mp_cont) wdf = move_parms.width(mp_fill) # Dilate {D} so that the contour touches the tips of rasters: dil = (wdf + wdc)/2 step = 0.25*wdf # Step to trace circular arcs (mm). PTCS = contour_points(dil, step) PCS = [] # List of contour paths. for i in range(len(PTCS)): PSi = PTCS[i] ph = path.from_points(PSi, mp_cont, None) pname = ("C%02d" % i) path.set_name(ph, pname, False) for j in range(path.nelems(ph)): mname = pname + (".%03d" % j) move.set_name(path.elem(ph, j), mname) PCS.append(ph) return PCS # ---------------------------------------------------------------------- def filling(fig, mp_fill, mp_jump): # Returns the lists {PFS} and {PLS} for {make_figure}. See the comments in that # function for the meaning of the parameters. wdf = move_parms.width(mp_fill) # Rasters: PTRS = raster_endpoints(wdf) PFS = raster.from_endpoints(PTRS, mp_fill) # Link paths: PTLS = link_points(wdf) PLS = [] for i in range(len(PTLS)): LSi = PTLS[i] # Links between scan-line {i} and scan-line {i+1} for j in range(len(LSi)): LSij = LSi[j] # Points on link path {j} above scan-line {i} LSij = snap_points_to_raster_ends(LSij, PTRS) assert len(LSij) > 0 pname = "L%02d.%d" % (i,j) ph = path.from_points(LSij, mp_fill, mp_jump) path.set_name(ph, pname, False) for k in range(path.nelems(ph)): mname = pname + (".%d" % k) move.set_name(path.elem(ph, k), mname) PLS.append(ph) # Attach the links to the raster paths, discardng sub-attached ones: PLSnew = [] for lk in PLS: lk = attach_link(lk, PFS); if lk != None: PLSnew.append(lk) PLS = PLSnew return PFS, PLS # ---------------------------------------------------------------------- def snap_points_to_raster_ends(PS, PTRS): # Given a list {PS} of points, and a list {PTRS} of all raster endpoint pairs # (as returned by {raster_endpoints}), returns a copy of {PS} where # every point that is close to a raster endpoint is replaced by that endpoint. # Repated points are removed from the list. tol = 1.0e-2 PSnew = [] for p in PS: for RSi in PTRS: # Raster endpoint pairs in a scan-line. for RSij in RSi: # A raster endpoint pair. for q in RSij: # A raster endpoint. if rn.dist(p, q) < tol: p = q if len(PSnew) == 0 or p != PSnew[-1]: PSnew.append(p) return PSnew # ---------------------------------------------------------------------- def attach_link(lk, PFS): # If both ends of the link path {lk} coincide with endpoints of # raster paths in {PFS}, attaches {lk} to those paths, in the proper orientations, # and returns {lk}. Otherwise (if only 0 or 1 of the ends of {lk} is attachable) # does nothing and returns {None} p = [ path.pini(lk), path.pfin(lk) ] PAS = [ paths_starting_at(PFS, p[elk]) for elk in range(2) ] if len(PAS[0]) == 0 or len(PAS[1]) == 0: sys.stderr.write("discarded not-biconnected link path\n") path.show(sys.stderr, lk, False, 1, 0, 0); sys.stderr.write("\n") return None else: # Connect {lk} to the paths {PAS[0],PAS[1]}: for elk in range(2): # Orient {lk} so that it ends at {p[elk]}: olk = path.rev(lk) if elk == 0 else lk for ors in PAS[elk]: path.add_link(ors, olk) return lk # ---------------------------------------------------------------------- def paths_starting_at(PFS, p): # Returns the paths in the list {PFS} that have an endpoint # practically coincident to {p}. The paths are reveresed as # needed so that they starts at {p}. tol = 1.0e-6 PAS = [] for rs in PFS: ors = None if rn.dist(p, path.pini(rs)) < tol: ors = rs if rn.dist(p, path.pfin(rs)) < tol: ors = path.rev(rs) if ors != None: PAS.append(ors) return PAS # ---------------------------------------------------------------------- def contacts(fig, PFS): # Creates contacts between the rasters in {PFS} xdir = (1,0) ydir = (0,1) CCS = raster.create_all_raster_raster_contacts(PFS, xdir, ydir) return CCS # ---------------------------------------------------------------------- def contact_graph(PFS, CCS): # Computes the vertices {VGS} and its edges {EGS} # of the contact graph for the filling, given the list of filling # raster paths {PFS} and the list of contacts {CCS}. # # The elements of {VGS} are points. The elements of {EGS} are # pairs of indices into {VGS}. # The vertices are the midpoints of the raster elements: VGS = [] for oph in PFS: p = path.pini(oph) q = path.pfin(oph) m = rn.mix(0.5, p, 0.5, q) VGS.append(m) # The edges are the pairs {(i,j)} such that there is contact # between {PFS[i]} and {PFS[j]}: EGS = [] for cc in CCS: mvs = contact.sides(cc) e = [None,None] for i in range(len(PFS)): ophi = PFS[i] assert path.nelems(ophi) == 1 omvi = path.elem(ophi,0) mvi, dri = move.unpack(omvi) for k in range(2): if mvs[k] == mvi: e[k] = i EGS.append(tuple(e)) return VGS,EGS # ---------------------------------------------------------------------- def make_figure(fig, mp_cont, mp_fill, mp_jump): PCS = contour(fig, mp_cont, mp_fill, mp_jump) PFS,PLS = filling(fig, mp_fill, mp_jump) CCS = contacts(fig, PFS) # VGS, EGS = contact_graph(PFS, CCS) return PCS,PFS,PLS,CCS,VGS,EGS # ---------------------------------------------------------------------- def plot_figure(name, fig,subfig, wdc,wdf, PCS,PFS,PLS,CCS,VGS,EGS): # sys.stderr.write(" ... contour paths ...\n") # path.show_list(sys.stderr, PCS, True, 2) # # sys.stderr.write(" ... filling elements ...\n") # path.show_list(sys.stderr, PFS, True, 2) # # if PLS != None and len(PLS) != 0: # sys.stderr.write(" ... link paths ...\n") # path.show_list(sys.stderr, PLS, True, 2) # # # Validate and show moves: # MVS = set() # for ph in PCS + PFS + PLS: # path.validate(ph) # for i in range(path.nelems(ph)): # mvi, dri = move.unpack(path.elem(ph, i)) # MVS.add(mvi) # if len(MVS) != 0: # sys.stderr.write(" ... moves ...\n") # move.show_list(sys.stderr, tuple(MVS), 2) if fig != "blocks" or subfig != 1: # Bounding box {B} encloses everything: B = None if len(PCS) != 0: B = rn.box_join(B, path.bbox(PCS)) if len(PFS) != 0: B = rn.box_join(B, path.bbox(PFS)) if len(PLS) != 0: B = rn.box_join(B, path.bbox(PLS)) B = rn.box_expand(B, (1.5, 1.5), (1.5, 1.5)) else: # Bounding box is specialized for the choices of a certain block: B = ((23.0, 9.0), (42.0, 17.0)) # X ranges of cut_lines: xlo = B[0][0] xhi = B[1][0] if fig != "blocks" or subfig != 1: # Enlarge bounding box for cut-line labels etc: B = rn.box_expand(B, (2.0, 0.0), (2.0, 0.0)) wd_axes = 0.15*min(wdf,wdc) # Width of jumps and axis lines. wd_ctac = wd_axes # Width of contact lines. rwd_fill = 0.60 # Relative width of link trace sausages. rwd_cont = 0.60 # Relative width of contour trace sausages. rwd_matter = 1.13 # Relative width of material footprint. mp_jump = move_parms.make(0, 3000, 100, 0.25) # Jump parameters. cwhite = pyx.color.rgb( 1.000, 1.000, 1.000 ) # Color for invisible frame. cmatter = pyx.color.rgb( 0.900, 0.870, 0.850 ) # Color for estimated material footprints. cfill = pyx.color.rgb( 0.000, 0.800, 0.050 ) # Color for relevant fill traces. ccont = pyx.color.rgb( 0.700, 0.750, 0.800 ) # Color for contours, when somewhat relevant. cghost = pyx.color.rgb( 0.850, 0.850, 0.850 ) # Color for non-relevant traces. cctac = pyx.color.rgb( 1.000, 0.200, 0.000 ) # Color for contacts. dp = None c, szx, szy = hacks.make_canvas(B, dp, False, False, 1, 1) wd_frame = 0.5*wd_axes hacks.plot_frame(c, cwhite, wd_frame, dp, B, wd_frame/2) plotted = False # True if something significant was plotted in this subfigure. if fig == "input" and subfig == 0: # Plot all the matter traces: jmp = False # Plot traces only. wd_matter = 0 # Extra width of material footprint. dashed = False wd_dots = 0 sz_arrows = 0 for ph in PCS + PFS + PLS: path.plot_layer(c, ph, dp, jmp, cmatter, rwd_matter, wd_matter, dashed, wd_dots, sz_arrows) if fig != "blocks" or subfig != 1: # Plot the contour paths with a subdued color: clr = ccont if fig == "input" and subfig == 0 else cghost axes = False dots = False arrows = False matter = False path.plot_standard(c, PCS, dp, None, [ccont,], rwd_cont, wd_axes, axes, dots, arrows, matter) if fig == "input": plotted = plot_figure_input(c, subfig, PCS, PFS, PLS, CCS, VGS, EGS, rwd_fill,wd_axes,wd_ctac, cfill,cctac,cghost) elif fig == "zigzig": plotted = plot_figure_scanline(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes,wd_ctac, cfill,cctac, False) elif fig == "zigzag": plotted = plot_figure_scanline(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes,wd_ctac, cfill,cctac, True) elif fig == "cold": plotted = plot_figure_cold(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes,wd_ctac, cfill,cctac) elif fig == "rivers": plotted = plot_figure_rivers(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes, xlo,xhi, cfill,cghost) elif fig == "canon": plotted = plot_figure_canon(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes, xlo,xhi, cfill,cghost) elif fig == "blocks": plotted = plot_figure_blocks(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes, xlo,xhi, cfill,cghost) else: sys.stderr.write("!! figure '%s ' not implemented.\n" % fig) if plotted: hacks.write_plot(c, name) else: sys.stderr.write("!! figure '%s subfigure %d' not plotted.\n" % (fig,subfig)) return # ---------------------------------------------------------------------- def plot_path_endpoints(c, PFS, wd_dots): # Plots the endpoints of the paths in {PFS}. cdots = pyx.color.rgb( 0.000, 0.000, 0.000 ) # Not used, really. for oph in PFS: for p in path.endpoints(oph): hacks.plot_line(c, cdots, wd_dots, None, p, p) return # ---------------------------------------------------------------------- def plot_figure_input(c, subfig, PCS, PFS, PLS, CCS, VGS, EGS, rwd_fill,wd_axes,wd_ctac, cfill,cctac,cghost): # Plots the figures with individual rasters, as slected by {subfig}: # # subfig = 0 rasters and contacts. # subfig = 1 links. # subfig = 2 contact graph. # # Returns true iff something was plotted. wd_fill = move_parms.width(move.parameters(path.elem(PFS[0],0))) # Ugly hack to get the raster trace width. plotted = False dp = None xdir = (1,0) ydir = (0,1) ystep,yphase = raster.get_spacing_and_phase(PFS, xdir, ydir) SCS = raster.separate_by_scanline(PFS, xdir,ydir, ystep,yphase) if subfig == 0: # RASTERS AND CONTACTS # Plot the filling path(s) with strong color: axes = False dots = False arrows = False matter = False path.plot_standard(c, PFS, dp, None, [cfill], rwd_fill, wd_axes, axes, dots, arrows, matter) # Plot contacts. wd_ctac = 1.5*wd_axes sz_tics = 0 arrows_ct = False for ct in CCS: contact.plot_single(c, ct, None, cctac, wd=wd_ctac, sz_tic=sz_tics, arrow=arrows_ct) plotted = True elif subfig == 1 and len(PLS) > 0: # LINKS # Plot the filling path(s) with weak color: axes = False dots = False arrows = False matter = False path.plot_standard(c, PFS, dp, None, [cghost,], rwd_fill, wd_axes, axes, dots, arrows, matter) # Plot individual links with various colors: clk0 = pyx.color.rgb( 1.000, 0.500, 0.000 ) clk1 = pyx.color.rgb( 0.000, 0.833, 1.000 ) rwd_link = 0.50 # Relative width of link trace sausages. axes = False dots = False arrows = False matter = False for i in range(len(SCS)): SCSi = SCS[i] # Rasters on scaline {i} for irs in SCSi: ors = PFS[irs] # A raster element on that scanline. ysc = path.pini(ors)[1] # Y coordinate of scanline. for olk in path.get_links(ors) + path.get_links(path.rev(ors)): # Orient the link to go up: if path.pini(olk)[1] > path.pfin(olk)[1]: olk = path.rev(olk) # Plot only links that go up from the current scanline: if path.pini(olk)[1] > ysc - 0.001*wd_fill: clk = (clk0, clk1)[i%2] path.plot_standard(c, [olk,], dp, None, [clk,], rwd_link, wd_axes, axes, dots, arrows, matter) # Plot dots at start and end of links: wd_dots = 0.75*rwd_fill*wd_fill plot_path_endpoints(c, PLS, wd_dots) plotted = True elif subfig == 2: # CONTACT GRAPH # Plot the filling path(s) with weak color: axes = False dots = False arrows = False matter = False path.plot_standard(c, PFS, dp, None, [cghost,], rwd_fill, wd_axes, axes, dots, arrows, matter) # Plot edges: cedges = pyx.color.rgb( 0.000, 0.000, 0.000 ) wd_edges = wd_axes for e in EGS: p = VGS[e[0]] q = VGS[e[1]] hacks.plot_line(c, cedges, wd_edges, None, p, q) # Plot vertices: cverts = pyx.color.rgb( 0.000, 0.000, 0.000 ) wd_verts = 0.9*rwd_fill*wd_fill # Diameter of vertices. for p in VGS: hacks.plot_line(c, cverts, wd_verts, None, p, p) plotted = True else: sys.stderr.write("!! invalid subfigure number %d.\n" % subfig) return plotted # ---------------------------------------------------------------------- def find_coldest_contact(oph, CCS): # Finds the contact in {CCS} that is closed by the path {oph} # and has the maximum cooling time. rcmax = -inf icmax = None ccmax = None for ic in range(len(CCS)): cc = CCS[ic] rc = contact.rcool(oph, cc) if rc != None and rc > rcmax: rcmax = rc icmax = ic ccmax = cc if ccmax != None: sys.stderr.write("max cooling time ratio = %.6f for contact CCS[%d]\n" % (rcmax,icmax)) else: assert False, "no contacts are closed by the path." return ccmax, rcmax # ---------------------------------------------------------------------- def plot_figure_scanline(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes,wd_ctac, cfill,cctac, alt): # Plots the figures with scanline path, alternating or not according to {alt}: # # subfig = 0 the path, with sected contact(s) # # Returns true iff something was plotted. wd_fill = move_parms.width(move.parameters(path.elem(PFS[0],0))) # Ugly hack to get the raster trace width. plotted = False assert subfig == 0 dp = None # Separate the rasters by scan-line: xdir = (1,0) ydir = (0,1) ystep, yphase = raster.get_spacing_and_phase(PFS, xdir, ydir) SCS = raster.separate_by_scanline(PFS, xdir, ydir, ystep, yphase) # Reverse alpernate scanlines and get list {CCSsel} of selected contacts to plot: if alt: # Reverse order and orientation of raster elements of {PFS} on alternate scanlines: PFSnew = [] # Reaster paths, reversed if needed. rev = False # True if next scan-line is to be reversed. for Si in SCS: # Now {Si} is a list of indices of rasters in one scanline. if rev: PFSi = [ path.rev(PFS[j]) for j in Si ] PFSi.reverse() else: PFSi = [ PFS[j] for j in Si ] rev = not rev PFSnew += PFSi PFS = PFSnew # Assemble the path: use_links = alt ph = path.concat(PFS, use_links, mp_jump) # Find the coldest contact and its cooling time: cc_cold, rcmax = find_coldest_contact(ph, CCS) CCSsel = [ cc_cold ] # Plot path: axes = False dots = False arrows = False matter = False path.plot_standard(c, [ph,], dp, None, [cfill,], rwd_fill, wd_axes, axes, dots, arrows, matter) # Plot the path endpoints: wd_dots = 0.75*rwd_fill*wd_fill plot_path_endpoints(c, [ph,], wd_dots) # Plot selected contact(s). wd_ctac = 1.5*wd_axes sz_tics = 0 arrows_ct = False for ct in CCSsel: contact.plot_single(c, ct, None, cctac, wd=wd_ctac, sz_tic=sz_tics, arrow=arrows_ct) # Put labels at ends of path: if alt: tdA = (-2.0, -2.0); tdB = (-2.0, +2.0) else: tdA = (-2.0, -2.0); tdB = (-2.0, +2.0) for lab, p in ((r"{\Huge\sf{A}}", rn.add(path.pini(ph), tdA)), (r"\sf{B}", rn.add(path.pfin(ph), tdB))): hacks.plot_text(c, 10, p, lab) plotted = True return plotted # ---------------------------------------------------------------------- def get_cold_paths(PFS): # Retruns a list {CRSS} that describes a typical tool-path produced by # {RP3} or {slic3r}. Each element of the list {CRSS} specifies the # rasters that comprise a snake sub-path ("continuous raster sequence) # of that tool-path. It is a list of pairs {(i,j)}, meaning raster {j} # of scanline {i}. CRSS = ( ( (0,0), (1,0), (2,0), (3,0), (4,0), (5,0), (6,0), (7,0), (8,0), (9,0), (10,0), (11,0), (12,0), (13,0), (14,0), ), ( (15,0), (16,0), (17,0), ), ( (14,1), (13,1), (12,1), (11,1), (10,1), (9,1), (8,1), (7,1), (6,1), (5,1), (4,1), (3,1), ), ( (0,1), (1,1), (2,1), (3,3), (4,3), (5,3), (6,3), (7,2), (8,2), (9,2), (10,2), (11,2), (12,2), (13,2), (14,2), ), ( (15,1), (14,3), (13,3), ), ( (12,3), (11,3), (10,3), (9,3), ), ( (6,2), (5,2), (4,2), (3,2), ), ) return CRSS # ---------------------------------------------------------------------- def plot_figure_cold(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes,wd_ctac, cfill,cctac): # Plots the figures with scanline path, alternating or not according to {alt}: # # subfig = 0 the path, with sected contact(s) # # Returns true iff something was plotted. wd_fill = move_parms.width(move.parameters(path.elem(PFS[0],0))) # Ugly hack to get the raster trace width. plotted = False assert subfig == 0 dp = None # Separate the rasters by scan-line: xdir = (1,0) ydir = (0,1) ystep, yphase = raster.get_spacing_and_phase(PFS, xdir, ydir) SCS = raster.separate_by_scanline(PFS, xdir, ydir, ystep, yphase) # Collect the rasters in the guessed {RP3} or {slic3r} order. CRSS = get_cold_paths(PFS) def choose_direction(p, ij): # Returns true if the raster identified by the index pair {ij} # should be reversed, based on which end is closer to {p}. i,j = ij oph = PFS[SCS[i][j]] d0 = rn.dist(p, path.pini(oph)) d1 = rn.dist(p, path.pfin(oph)) return d0 > d1 # .................................................................... # Join each Alternate directions between adjacent scanlines. p_prev = (15,0) # Last position of the nozzle. EFS = [] for CRS in CRSS: # Decide orientation {rev} of first raster in {CRS} rev = choose_direction(p_prev, CRS[0]) # Now collect the raster fill paths specified by {CRS}, alternating directions: for i,j in CRS: oph = PFS[SCS[i][j]] if rev: oph = path.rev(oph) EFS.append(oph) p_prev = path.pfin(oph) rev = not rev # Assemble the path: use_links = True ph = path.concat(EFS, use_links, mp_jump) # Find the coldest contact and its cooling time: cc_cold, rcmax = find_coldest_contact(ph, CCS) CCSsel = [ cc_cold ] # SPlit at jumps for coloring: OCRS, JMPS = path.split_at_jumps(ph) CLRS = hacks.trace_colors(len(OCRS)) # Plot trace components: axes = False dots = False arrows = False matter = False path.plot_standard(c, OCRS, dp, None, CLRS, rwd_fill, wd_axes, axes, dots, arrows, matter) # Plot the jumps: cjump = pyx.color.rgb( 0.000, 0.000, 0.000 ) # Just in case. axes = True dots = True arrows = True matter = False move.plot_standard(c, JMPS, dp, 3, [cjump,], rwd_fill, wd_axes, axes, dots, arrows, matter) # Plot the path endpoints: wd_dots = 0.75*rwd_fill*wd_fill plot_path_endpoints(c, [ph,], wd_dots) # Plot selected contact(s). wd_ctac = 1.5*wd_axes sz_tics = 0 arrows_ct = False for ct in CCSsel: contact.plot_single(c, ct, None, cctac, wd=wd_ctac, sz_tic=sz_tics, arrow=arrows_ct) plotted = True return plotted # ---------------------------------------------------------------------- def get_rivers(PFS, CCS): # Retruns a list {GROUPS} that describes the rivers in the filling raster set {PFS} # defined by the contacts {CCS}. Each element of the list {GROUPS} specifies the # rasters that comprise a river. It is a list of pairs {(i,j)}, meaning raster {j} # of scanline {i}. # !!! Should use {raster_regroup.split_by_group} !!! GROUPS = ( ( (0,0), (1,0), (2,0), ), ( (3,0), (4,0), (5,0), (6,0), (7,0), (8,0), (9,0), (10,0), (11,0), (12,0), (13,0), (14,0), ), ( (3,1), (4,1), (5,1), (6,1), (7,1), (8,1), (9,1), (10,1), (11,1), (12,1), (13,1), (14,1), ), ( (15,0), (16,0), (17,0), ), ( (14,2), (13,2), ), ( (13,3), (14,3), (15,1), ), ( (9,2), (10,2), (11,2), (12,2), ), ( (9,3), (10,3), (11,3), (12,3), ), ( (7,2), (8,2), ), ( (3,3), (4,3), (5,3), (6,3), ), ( (3,2), (4,2), (5,2), (6,2), ), ( (0,1), (1,1), (2,1), ), ) return GROUPS # ---------------------------------------------------------------------- def get_sub_rivers(PFS, CCS): # Retruns a list {GROUPS} that describes the sub-rivers in the filling raster set {PFS} # defined by the contacts {CCS}. # # Each element of the list {GROUPS} specifies the rasters that comprise # a sub-river. It is a list of pairs {(i,j)}, meaning raster {j} of # scanline {i}. # !!! Should use {raster_regroup.split_by_group} !!! GROUPS = ( ( (0,0), (1,0), (2,0), ), ( (0,1), (1,1), (2,1), ), ( (3,0), (4,0), (5,0), (6,0), ), ( (3,1), (4,1), (5,1), (6,1), ), ( (3,2), (4,2), (5,2), (6,2), ), ( (3,3), (4,3), (5,3), (6,3), ), ( (7,0), (8,0), ), ( (7,1), (8,1), ), ( (7,2), (8,2), ), ( (9,0), (10,0), (11,0), (12,0), ), ( (9,1), (10,1), (11,1), (12,1), ), ( (9,2), (10,2), (11,2), (12,2), ), ( (9,3), (10,3), (11,3), (12,3), ), ( (13,0), (14,0), ), ( (13,1), (14,1), ), ( (13,2), (14,2), ), ( (13,3), (14,3), ), ( (15,0), ), ( (15,1), ), ( (16,0), (17,0), ), ) return GROUPS # ---------------------------------------------------------------------- def get_essential_cut_lines(PFS, CCS): # Returns a list {LNS} of essential cut-lines. # Each element of {LNS} is a pair {(i, t)} where {i} is the index of the scan-line just above # the cut-line and {t} is a code (2 for essential, 1 for non-essential, 0 for not relevant). LNS = ( (0,2), (3,2), (7,2), (9,2), (13,2), (15,2), (16,2), (18,2), ) return LNS # ---------------------------------------------------------------------- def plot_cut_lines(c, LNS, xlo,xhi,ystep,yphase, wd_axes): # The parameter {LNS} must be a list of pairs {(i, t)}, as in the # output of {get_essential_cut_lines}. # Plots the essential ({t=2}) and non-essential ({t=1}) cut-lines listed in {LNS}. # Ignores cut-lines with {t=0} wd_cuts = wd_axes cess = pyx.color.rgb( 1.000, 0.200, 1.000 ) # Color of essential cut-lines. cnon = pyx.color.rgb( 0.400, 0.400, 1.000 ) # Colot of non-essential cut-lines. for i, t in LNS: if t != 0: clr = cess if t == 2 else cnon dashpat = [ 0.100, 0.250 ] if t == 1 else None y = ystep*(i-0.5) + yphase p = (xlo, y) q = (xhi, y) hacks.plot_line(c, clr, wd_cuts, dashpat, p, q) return # ---------------------------------------------------------------------- def plot_figure_rivers(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes, xlo,xhi, cfill,cghost): # Plots the figure with rasters grouped into rivers. # # Returns true iff something was plotted. wd_fill = move_parms.width(move.parameters(path.elem(PFS[0],0))) # Ugly hack to get the raster trace width. # Separate the rasters by scan-line: xdir = (1,0) ydir = (0,1) ystep, yphase = raster.get_spacing_and_phase(PFS, xdir, ydir) SCS = raster.separate_by_scanline(PFS, xdir, ydir, ystep, yphase) # Assign group indices to rivers. # Each element of the list {GROUPS} below is a river. # Each river is a list of pairs {(i,j)}, meaning raster {j} of scanline {i}. plotted = False dp = None if subfig == 0: GROUPS = get_rivers(PFS, CCS) elif subfig == 1: GROUPS = get_sub_rivers(PFS, CCS) else: assert False, "invalid subfig" ngr = len(GROUPS) igr = 0 for RIV in GROUPS: # Assign group index {igr} to rasters in {RIV}: for i,j in RIV: oph = PFS[SCS[i][j]] assert path.nelems(oph) == 1 path_hp.set_group(oph, igr) igr += 1 assert igr == ngr # Make a color list, one for each river: CLRS = hacks.trace_colors(ngr) # Plot the filling path(s) with color based on group: for oph in PFS: igr = path_hp.get_group(oph) axes = False dots = False arrows = False matter = False path.plot_standard(c, [oph,], dp, None, [CLRS[igr],], rwd_fill, wd_axes, axes, dots, arrows, matter) if subfig == 1: LNS = get_essential_cut_lines(PFS, CCS); plot_cut_lines(c, LNS, xlo,xhi,ystep,yphase, wd_axes) plotted = True return plotted # ---------------------------------------------------------------------- def get_canon_cut_lines(PFS, CCS, ictmax): # Returns a list {LNS} of essential and non-essential cut-lines to plot on the canonical path figure. # Each element of {LNS} is a pair {(i, t)} where {i} is the index of the scan-line just above # the cut-line and {t} is a code (2 for essential, 1 for non-essential, 0 for not relevant). LNS = get_essential_cut_lines(PFS, CCS) LNS += ( (5,1), (12,1), ) # Remove excess cut lines: LNS = [ (i, t) for i, t in LNS if i <= ictmax ] return LNS # ---------------------------------------------------------------------- def get_canon_snake_paths(PFS,SCS,ictmax,blocks,mp_jump): # Returns the canonical snake paths/blocks {SNAKES} for the canonical path figure, # and a list {PFSrest} with the rasters of {PFS} not used in the snakes. # # The parameter {PFS} should be a list of all the filling raster elements. # each element oriented from left to right. # # The parameter {SCS} should be a list of lists of indices. The raster # element {j} (from 0, left to right) on scanline {i}, as returned by # {raster.separate_by_scanline}. # # If {blocks} is false, retruns a list {SNAKES} with the canonical # bands and snake paths of the canonical path figure. Each element of # the list {SNAKES} is a list of snake paths that belong to a band of # the figure. The bands will be defined by the essential cut-lines # plus a few arbitrary non-essential ones. # # If {blocks} is true, the {SNAKES} list will contain canonical blocks # instead of canonical snake paths; where wach block is either 2 or 4 # snake paths, depending on the number of scan-lines that it spans. # # The {SNAKES} list will stop at the band whose top cut-line has # index {ictmax}. The rasters in scan-lines above that cut-line # are returned in {PFSrest}, as found in {PFS}. # !!! Should use {raster_regroup.split_by_group} !!! # {SNIXS} is a list of list of list of pairs. If {SNIXS[b][s][r]} is {(i,j)}, # it means that raster {r} (from bottom) of snake {s} (from left) on band {b} (from bottom) # is raster {j} of scanline {i}. SNIXS = ( # band (0,3): ( ( (0,0), (1,0), (2,0), ), ( (0,1), (1,1), (2,1), ), ), # Band (3,5): ( ( (3,0), (4,0), ), ( (3,1), (4,1), ), ( (3,2), (4,2), ), ( (3,3), (4,3), ), ), # Band (5,7): ( ( (5,0), (6,0), ), ( (5,1), (6,1), ), ( (5,2), (6,2), ), ( (5,3), (6,3), ), ), # Band (7,9): ( ( (7,0), (8,0), ), ( (7,1), (8,1), ), ( (7,2), (8,2), ), ), # Band (9,12): ( ( (9,0), (10,0), (11,0), ), ( (9,1), (10,1), (11,1), ), ( (9,2), (10,2), (11,2), ), ( (9,3), (10,3), (11,3), ), ), # Band (12,13): ( ( (12,0), ), ( (12,1), ), ( (12,2), ), ( (12,3), ), ), # Band (13,15): ( ( (13,0), (14,0), ), ( (13,1), (14,1), ), ( (13,2), (14,2), ), ( (13,3), (14,3), ), ), # Band (15,16): ( ( (15,0), ), ( (15,1), ), ), # Band (16,18): ( ( (16,0), (17,0), ), ), ) SNAKES = [] # List of lists of canonical snake paths/blocks. PFSrest = [] for SNBIXS in SNIXS: # SNBIXS is a list of list of index pairs, for one band. # Create snakes/blocks of that band: PFSband = [] # List of canonical snake paths in band. for RIXS in SNBIXS: # RIXS is a list of index pairs of the rasters in one canonical snake. # Create that canonical band snake/block {snk} snk = make_canon_snake_path_block(RIXS, SCS, PFS, blocks, ictmax, mp_jump, PFSrest) if snk != None: PFSband.append(snk) if len(PFSband) != 0: SNAKES.append(PFSband) sys.stderr.write("got %d bands and %d leftover rasters\n" % (len(SNAKES), len(PFSrest))) for ibd in range(len(SNAKES)): SNB = SNAKES[ibd] sys.stderr.write(" band %d has %d snakes\n" % (ibd,len(SNB))); return SNAKES, PFSrest # ---------------------------------------------------------------------- def make_canon_snake_path_block(RIXS, SCS, PFS, blocks, ictmax, mp_jump, PFSrest): # Parameters {SCS}, {PFS}, {blocks}, {ictmax} are as in {get_canon_snake_paths}. # Parameter {RIXS}, is a list of pairs {(i,j)} that identify the rasters of # one canonical snake path/block. Returns that snake/block. # # Specifically, if {blocks} is false, returns the canonical band snake as a # single path. If {blocks} is true, returns a block whose choices are the 2 or 4 # snake paths that can be built from those same rasters. # # However, only rasters below the cut-line {ictmax} are used in the snake path/block. # Rasters above that cut-line are instead appended to the list {PFSrest} # with no change. If all rasters specified by {RIXS} are above that cut-line, # returns {None}. PBS0 = [] # Raster elemens in this snake. PBS1 = [] # Reversed raster elemens in this snake (if {blocks}). rev = False # Should reverse the next raster? for i, j in RIXS: ors = (PFS[SCS[i][j]]) if i >= ictmax: PFSrest.append(ors) else: if rev: ors = path.rev(ors) PBS0.append(ors) if blocks: PBS1.append(path.rev(ors)) rev = not rev # Did we ger any rasters at all? if len(PBS0) == 0: return None # Now join the rasters in each of {PBS0} and {PBS1} into a snake/block use_links = True ph0 = path.concat(PBS0, use_links, mp_jump) # The snake path. if not blocks: return ph0 else: ph1 = path.concat(PBS1, use_links, mp_jump) # The snake path. if len(PBS0) == 1: blk = block.from_paths([ph0, ph1]) else: blk = block.from_paths([ph0, path.rev(ph0), ph1, path.rev(ph1)]) return blk # ---------------------------------------------------------------------- def plot_figure_canon(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes, xlo,xhi, cfill,cghost): # Plots the figure with canonical paths and canonical bandpath. # # Returns true iff something was plotted. wd_fill = move_parms.width(move.parameters(path.elem(PFS[0],0))) # Ugly hack to get the raster trace width. # Separate the rasters by scan-line: xdir = (1,0) ydir = (0,1) ystep, yphase = raster.get_spacing_and_phase(PFS, xdir, ydir) SCS = raster.separate_by_scanline(PFS, xdir, ydir, ystep, yphase) # Assign group indices to rivers. # Each element of the list {SNAKES} below is a river. # Each river is a list of pairs {(i,j)}, meaning raster {j} of scanline {i}. plotted = False dp = None ictmax = 12 blocks = False SNAKES, PFSrest = get_canon_snake_paths(PFS,SCS,ictmax,blocks,mp_jump) LNS = get_canon_cut_lines(PFS, CCS, ictmax); plot_cut_lines(c, LNS, xlo,xhi,ystep,yphase, wd_axes) # merge the {SNAKES} paths into a canonical path {cph} and the following # canonical band-path {bph}: nbd = len(SNAKES) CPSS = [] # List of canonical snakes that make the canonica path {cph} for ibd in range(nbd-1): CPSS += SNAKES[ibd] use_links = True cph = path.concat(CPSS, use_links, mp_jump) bph = path.concat(SNAKES[nbd-1], use_links, mp_jump) # Plot the canonical path and the canonical sub-path: ccph = cfill # Color for the canonical path. cbph = pyx.color.rgb(0.000, 0.600, 1.000) # Color for the canonical band-path. axes = False dots = False arrows = False matter = False path.plot_standard(c, [cph,], dp, None, [ccph,], rwd_fill, wd_axes, axes, dots, arrows, matter) path.plot_standard(c, [bph,], dp, None, [cbph,], rwd_fill, wd_axes, axes, dots, arrows, matter) # Plot the path endpoints: wd_dots = 0.75*rwd_fill*wd_fill plot_path_endpoints(c, [cph,], wd_dots) plot_path_endpoints(c, [bph,], wd_dots) # Plot the unused rasters: for oph in PFSrest: path.plot_standard(c, [oph,], dp, None, [cghost,], rwd_fill, wd_axes, axes, dots, arrows, matter) plotted = True return plotted # ---------------------------------------------------------------------- def plot_figure_blocks(c, subfig, PCS, PFS, PLS, CCS, mp_jump, rwd_fill,wd_axes, xlo,xhi, cfill,cghost): # If {subfig} is 0, plots the figure with canonical paths and canonical bandpath. # If {subfig} is 0, plots the four alternatives of a selected block. # # Returns true iff something was plotted. wd_fill = move_parms.width(move.parameters(path.elem(PFS[0],0))) # Ugly hack to get the raster trace width. # Separate the rasters by scan-line: xdir = (1,0) ydir = (0,1) ystep, yphase = raster.get_spacing_and_phase(PFS, xdir, ydir) SCS = raster.separate_by_scanline(PFS, xdir, ydir, ystep, yphase) nsc = len(SCS) # Number of scan-lines. # Assign group indices to rivers. # Each element of the list {SNAKES} below is a river. # Each river is a list of pairs {(i,j)}, meaning raster {j} of scanline {i}. plotted = False dp = None ictmax = nsc blocks = True SNAKES, PFSrest = get_canon_snake_paths(PFS,SCS,ictmax,blocks,mp_jump) LNS = get_canon_cut_lines(PFS, CCS, ictmax); assert len(PFSrest) == 0 if subfig == 0: plot_cut_lines(c, LNS, xlo,xhi,ystep,yphase, wd_axes) # Plot the blocks: axes = False dots = False arrows = False matter = False for SNBS in SNAKES: # SNBS is a list of blocks from one band. for blk in SNBS: nch = block.nchoices(blk) for ich in range(nch): oph = block.choice(blk, ich) path.plot_standard(c, [oph,], dp, None, [cfill,], rwd_fill, wd_axes, axes, dots, arrows, matter) plotted = True elif subfig == 1: # Select a block: blk = SNAKES[4][2] assert block.nchoices(blk) == 4 # Define the displacements between plots: Xdp = 10.0 Ydp = 4.0 axes = True dots = True arrows = True matter = False for ixp in range(2): for iyp in range(2): dp = ( 0.0 + ixp*Xdp, 0.0 + iyp*Ydp ) ich = 2*ixp + iyp oph = block.choice(blk, ich) path.plot_standard(c, [oph,], dp, None, [cfill,], rwd_fill, wd_axes, axes, dots, arrows, matter) plotted = True else: assert False, "invalid subfig %d\n" % subfig return plotted # ----------------------------------------------------------------------