# Implementation of {raster}. # Last edited on 2021-10-31 05:48:00 by stolfi import raster import path import contact import move import move_parms import block import rootray import rootray_shape import rootray_cart import hacks import rn import rmxn import sys from math import sqrt, sin, cos, atan2, log, exp, floor, ceil, inf, nan, pi def get_spacing_and_phase(OPHS, xdir, ydir): nph = len(OPHS) assert nph > 0, "no rasters given" # Guess the scanline spacing {ystep}: totwd = 0 for oph in OPHS: assert path.nelems(oph) == 1 mv = path.elem(oph, 0) assert not move.is_jump(mv) totwd += move.width(mv) ystep = totwd/nph assert ystep > 1.0e-6 # Find the lowest raster in {ydir} direction and its projection {ymin}: ymin = +inf for oph in OPHS: xm, ym = path.mean_projections(oph, xdir, ydir) ymin = min(ymin, ym) if nph == 1: # We can use {ymin} as the phase: yphase = ymin else: # Take {ymin} as the guesed phase and refine it so that the mean dev is zero: sum_yr = 0 isc_min = +inf for oph in OPHS: xm, ym = path.mean_projections(oph, xdir, ydir) yd = ym - ymin isc = floor(yd/ystep + 0.5) yr = yd - ystep*isc assert abs(yr) < 0.4*ystep, "raster deviation from scanline too large" isc_min = min (isc, isc_min) sum_yr += yr yphase = ymin + sum_yr/nph + isc_min*ystep # Check for quantized spacing: for oph in OPHS: xm, ym = path.mean_projections(oph, xdir, ydir) yd = ym - yphase yr = yd - ystep*floor(yd/ystep + 0.5) # Deviation from ideal position. assert abs(yr) < 0.2*ystep return ystep, yphase # ---------------------------------------------------------------------- def point_scanline_index(p, xdir, ydir, ystep, yphase): ym = rn.dot(p, ydir) yd = ym - yphase isc = int(floor(yd/ystep + 0.5)) return isc # ---------------------------------------------------------------------- def path_scanline_index(oph, xdir, ydir, ystep, yphase): xm, ym = path.mean_projections(oph, xdir, ydir) yd = ym - yphase isc = int(floor(yd/ystep + 0.5)) return isc # ---------------------------------------------------------------------- def sort_by_scanline(OPHS, xdir, ydir, ystep, yphase): nph = len(OPHS) if nph <= 1: return OPHS.copy() def scanpos(oph): # Returns a pair {(isc, xm)} where {isc} is the index # of the scanline nearest to the raster fill element {oph}, # and {xm} is its mean coordinate along the direction {xdir}. xm, ym = path.mean_projections(oph, xdir, ydir) isc = int(floor((ym - yphase)/ystep + 0.5)) return (isc, xm) OPHS_srt = sorted(OPHS, key = scanpos) return OPHS_srt # ---------------------------------------------------------------------- def separate_by_scanline(OPHS, xdir, ydir, ystep, yphase): SCS = [] # List of lists of indices of fillers in each scan-line. isc_prev = None # Scanline index of {oph_prev}. for iph in range(len(OPHS)): oph_this = OPHS[iph] # Get scanline index: isc_this = path_scanline_index(oph_this, xdir, ydir, ystep, yphase) assert isc_this >= 0, "negative scanline index -- check {yphase}" if isc_this == isc_prev : # Looks like they are on the same scanline: assert len(SCS) > isc_this assert len(SCS[isc_this]) > 0 elif isc_prev == None or isc_this > isc_prev: # Looks like we got to a new scanline. Extend {SCS}: while len(SCS) <= isc_this: SCS.append([].copy()) else: assert False, "rasters are not in scanline order" SCS[isc_this].append(iph) isc_prev = isc_this return SCS # ---------------------------------------------------------------------- def make_raster_raster_link(oph0, oph1, xdir, dmax, dxmid, mp_link): omv0 = path.elem(oph0,0) omv1 = path.elem(oph1,0) wdl = move_parms.width(mp_link) p0 = move.pini(omv0); v0 = rn.sub(move.pfin(omv0), p0); dx0 = rn.dot(v0, xdir) p1 = move.pini(omv1); v1 = rn.sub(move.pfin(omv1), p1); dx1 = rn.dot(v1, xdir) if dx0*dx1 > 1.0e-8 and rn.dist(p0,p1) <= dmax: if dxmid == None: lk = path.from_points([p0, p1,], mp_link, None) else: dxm = -dxmid if dx0 > 0 else +dxmid pm = rn.mix3(0.5,p0, 0.5,p1, dxm,xdir) lk = path.from_points([p0, pm, p1,], mp_link, None) path.add_link(oph0, path.rev(lk)) path.add_link(oph1, lk) else: lk = None return lk # ---------------------------------------------------------------------- def make_raster_raster_contact(oph0,oph1, xdir, szmin, rszmin, tol): assert path.nelems(oph0) == 1 assert path.nelems(oph1) == 1 mv0, dr0 = move.unpack(path.elem(oph0,0)) mv1, dr1 = move.unpack(path.elem(oph1,0)) mvdir = xdir ct = contact.from_moves(mv0, mv1, mvdir, szmin, rszmin, tol) if ct != None: for isd in range(2): oph = (oph0,oph1)[isd] path.add_contact(oph, isd, ct) contact.add_side_path(ct, isd, oph, 0) return ct # ---------------------------------------------------------------------- def create_all_raster_raster_links_and_contacts(OPHS, xdir, ydir, dmax, dxmid, mp_link): debug = False LKS = [] CTS = [] if len(OPHS) > 0: # Paranoia: mp_fill = move.parameters(path.elem(OPHS[0],0)) wd_fill = move_parms.width(mp_fill) for oph in OPHS: assert path.nelems(oph) == 1 omv = path.elem(oph, 0) assert not move.is_jump(omv) assert move.parameters(omv) == mp_fill wdf = move_parms.width(mp_fill) ystep, yphase = get_spacing_and_phase(OPHS, xdir, ydir) OPHS = sort_by_scanline(OPHS, xdir, ydir, ystep, yphase) SCS = separate_by_scanline(OPHS, xdir, ydir, ystep, yphase) nsc = len(SCS) # Number of scanlines. for isc in range(nsc-1): # Get the raster lists of two conseutive scanlines: SC0 = SCS[isc]; SC1 = SCS[isc+1]; if len(SC0) > 0 and len(SC1) > 0: for irs0 in range(len(SC0)): oph0 = OPHS[SC0[irs0]] nars0 = ("%d" % isc) if len(SC0) == 1 else ("%d.%d" % (isc,irs0)) if debug: path.show(sys.stderr, " oph0 = ", oph0, "\n", True, 0,0,0); for irs1 in range(len(SC1)): oph1 = OPHS[SC1[irs1]] nars1 = ("%d" % (isc+1)) if len(SC0) == 1 else ("%d.%d" % (isc+1,irs1)) if debug: path.show(sys.stderr, " oph1 = ", oph1, "\n", True, 0,0,0) # Create link paths: for kdr0 in range(2): oph0r = path.spin(oph0,kdr0) nadr0 = "az"[kdr0] for kdr1 in range(2): oph1r = path.spin(oph1,kdr1) nadr1 = "az"[kdr1] lk = make_raster_raster_link(oph0r, oph1r, xdir, dmax, dxmid, mp_link) if lk != None: LKS.append(lk) name = "L[%s%s:%s%s]" % (nars0,nadr0,nars1,nadr1) path.set_name(lk, name, True) if debug: path.show(sys.stderr, " created link ", lk, "\n", True, 0,0,0) # Create the contacts: szmin = 1.1 * wd_fill rszmin = 0.01 tol = 0.20*wd_fill ct = make_raster_raster_contact(oph0,oph1, xdir, szmin, rszmin, tol) if ct != None: name = "C[%s:%s]" % (nars0,nars1) contact.set_name(ct, name) if debug: contact.show(sys.stderr, " created contact", ct, "\n", 0) CTS.append(ct) return LKS, CTS # ---------------------------------------------------------------------- def endpoints_from_polys(PTCS, xdir,ydir, ystep,yphase): # Get the bounding box of the points, expanded: B = None for PTS in PTCS: for p in PTS: B = rn.box_include_point(B, p) mrg = (2,2) # 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}, assuming that the outermost # polygon is CCW (an island): SH = rootray_shape.from_polygons(PTCS, +1) # Obtain the rasters by stabbing it: PTRS = endpoints_from_shape(SH, B, xdir,ydir, ystep,yphase) return PTRS # ---------------------------------------------------------------------- def endpoints_from_shape(S, B, xdir,ydir, ystep,yphase): debug = False # Compute approx range of X and of scan-line indices (possibly too large): Adir = ((xdir[0], ydir[0]), (xdir[1], ydir[1])) # Matrix that maps (xdir,ydir) to standard basis. Brot = rmxn.box_affine(B, Adir, (0,0)) xlo = Brot[0][0] - 10; xhi = Brot[1][0] + 10; ylo = Brot[0][1] - 2*ystep; yhi = Brot[1][1] + 2*ystep; isc_lo = int(floor((ylo - yphase)/ystep)) isc_hi = int(ceil((yhi - yphase)/ystep)) nsc = isc_hi - isc_lo + 1 # Number of scan-lines in filling. # Clip the shape {S} to the box {B}: K = rootray_shape.box(B[0], B[1]) S = rootray_shape.intersection([S, K]) PTRS = [] nsg = 0 # Number of line segments (endpoint pairs). sys.stderr.write("filling has %d scan-lines {%d..%d}\n" % (nsc, isc_lo, isc_hi)) for di in range(nsc): isc = isc_lo + di y = isc*ystep + yphase # Ordinate of axis of scan-line. # Map to {xdir,ydir} system and compute scan-line ref points {p,q}: p = rn.mix(xlo, xdir, y, ydir) q = rn.mix(xhi, xdir, y, ydir) # sys.stderr.write("@@ scanline %d y = %d p = %s q = %s\n" % (isc, y, str(p), str(q))) # Intersect scanl-line axis with shape {S}: sgn1, TS = rootray_shape.trace(p, q, S) assert sgn1 == +1 nt = len(TS) assert nt % 2 == 0, "odd ray-polygon intersection count" # Collect the pairs of endpoints: PTRSi = [] # List of endpoint pairs on scanline {ìsc} for jrs in range(nt//2): t0 = TS[2*jrs] t1 = TS[2*jrs + 1] # sys.stderr.write(" raster %d t = [%12.8f _ %12.8f]\n" % (jrs, t0, t1)) pr = rn.mix(1-t0, p, t0, q) qr = rn.mix(1-t1, p, t1, q) PTRSij = (pr, qr) PTRSi.append(PTRSij) nsg += 1 PTRS.append(PTRSi) # Trim empty scan-lines from each end: while len(PTRS) > 0 and len(PTRS[0]) == 0: PTRS.pop(0) while len(PTRS) > 0 and len(PTRS[-1]) == 0: PTRS.pop() if debug: sys.stderr.write("{endpoints_from_shape}: found %d non-empty scanlines and %d segments\n" % (len(PTRS), nsg)) return PTRS # ---------------------------------------------------------------------- def from_endpoints(PTRS, mp): debug = False OPHS = [] for isc in range(len(PTRS)): PTRSi = PTRS[isc] # rasters on scan-line {isc} for jrs in range(len(PTRSi)): PTSRij = PTRSi[jrs] # Endpoints of raster {jrs} on scan-line {isc}. assert len(PTSRij) == 2 p = PTSRij[0] q = PTSRij[1] mv = move.make(p, q, mp) mvname = "T%03d.%02d" % (isc, jrs) move.set_name(mv, mvname) ph = path.from_moves([mv,]) phname = "R%03d.%02d" % (isc, jrs) path.set_name(ph, phname, False) OPHS.append(ph) if debug: sys.stderr.write("{from_endpoints}: built %d raster paths\n" % (len(OPHS))) return OPHS # ---------------------------------------------------------------------- def link_points_from_raster_endpoints(PTCS, PTRS, ydir): # Slice the perimeter of {D} by the bands between successive scanlines: debug = False PTLS = [] nsc = len(PTRS) # Number of scan-lines. nlk = 0 # Number of link paths nbd = 0 # Number of inter-line bands with some links. for isc0 in range(nsc-1): isc1 = isc0 + 1 # Create list {LS} of link paths in the strip between scan-lines {isc0} and {isc1} LS = [] RS0 = PTRS[isc0] # List of raster endpoint pairs on scan-line {isc0} RS1 = PTRS[isc1] # List of raster endpoint pairs on scan-line {isc1} if len(RS0) != 0 and len(RS1) != 0: # Get Y coordinates of the two scan-lines: y0 = rn.dot(RS0[0][0], ydir) y1 = rn.dot(RS1[0][0], ydir) for CS in PTCS: LSj = hacks.cut_poly_with_band(CS, ydir, y0, y1) LS += LSj nlki = len(LS) nlk += nlki if nlki > 0: nbd += 1 PTLS.append(LS) if debug: sys.stderr.write("{link_points_from_raster_endpoints}: processed %d scanlines got %d link paths in %d bands\n" % (nsc,nlk,nbd)) return PTLS # ----------------------------------------------------------------------