# Implementation of module {path.py} # Last edited on 2021-02-18 21:25:44 by jstolfi import path import move import hacks import rn import pyx from math import nan, inf, sqrt, sin, cos, pi, floor import sys class Path_IMP: # The initial and final points of the {Path} object, in its native # orientation, are {ph.pt[0]} and {ph.pt[1]}, respectively. # # The list of oriented moves (traces and jumps) that comprise a path # {ph}, in the native order, is {ph.omv}. Namely, tracing the oriented # path {(ph,0)} means executing the oriented moves # {ph.omv[0],ph.omv[1],...,ph.omv[n-1]}, in this sequence, where # {n=len(ph.omv)}. Tracing the oriented path {(ph,1)} means executing # {rev(ph.omv[n-1]),rev(ph.omv[n-2]),...,rev(ph.omv[0])}, in this # sequence. # # The field {ph.cumtex} is a list such that {ph.cumtex[k]} is the # cumulative time to execute moves {ph.omv[0]} through {ph.omv[k]}, # for each {k} in {0..n-1}. These times assume that the nozzle is # stationary at the beginning and end of each move, so that # {ph.cumtex[k]} is just the sum of the execution times of the # individual moves. def __init__(self, p, q, omv, cumtex): self.omv = omv self.cumtex = cumtex self.pt = (p, q) def nelems(oph): ph, dr = unpack(oph) return len(ph.omv) def elem(oph, k): ph, dr = unpack(oph) # sys.stderr.write("oph = %s ph = %s dr = %s\n" % (str(oph), str(ph), str(dr))) if dr == 0: return ph.omv[k] else: n = len(ph.omv) return move.rev(ph.omv[n-1-k]) def find(oph, omv): n = nelems(oph) mv, dr = move.unpack(omv) for k in range(n): omvk = elem(oph,k) mvk, drk = move.unpack(omvk) if mvk == mv: return k return None def pini(oph): ph, dr = unpack(oph) return ph.pt[dr] def pfin(oph): ph, dr = unpack(oph) return ph.pt[1-dr] def bbox(oph): n = nelems(oph) B = rn.box_from_point(pini(oph)) # In case the path is empty. for k in range(n): omvk = elem(oph, k) Bk = move.bbox(omvk) B = rn.box_join(B, Bk) return B # ---------------------------------------------------------------------- def make_empty(p): return path.Path(p, p, (), ()) def from_move(p, q, wd, parms): mv = move.make(p, q, wd, parms) return path.Path(p, q, ( mv, ), ( move.extime(mv), ) ) def from_moves(omvs): assert type(omvs) is list or type(omvs) is tuple n = len(omvs) # Number of moves. assert n >= 1, "must have at least one move" p = move.pini(omvs[0]) # Initial point of final path. q = move.pfin(omvs[n-1]) # Final point of final path. pant = p tant = 0 cumtex = [] for omvk in omvs: mvk, drk = move.unpack(omvk) # For type checking. pk = move.pini(omvk) qk = move.pfin(omvk) assert pk == pant, "moves are not connected: %s %s" % (str(pant),str(pk)) tfink = tant + move.extime(omvk) cumtex.append(tfink) pant = qk tant = tfink assert pant == q return path.Path(p, q, tuple(omvs), tuple(cumtex)) def from_points(pts, wd, parms): assert type(pts) is list or type(pts) is tuple m = len(pts) # Number of points or lists. assert m >= 1, "must have at least one point" pant = None omvs = [] cumtex = [] jmp = None # True if previous element of {pts} was list, false if it was point. for ptj in pts: if hacks.is_point(ptj): # Append a single move with the given width: if pant != None: mv = move.make(pant, ptj, wd, parms) omvs.append(mv) pant = ptj jmp = False else: # {ptj} must be a list of points. Appends a sequence of moves from {pant} # to those points. The first move, if any, will be a jump, the rest # will be traces. Then forces a jump to whatever comes next (if any). assert type(ptj) is list or type(ptj) is tuple wdjk = 0 # Width of next move. for ptjk in ptj: assert hacks.is_point(ptjk) if pant != None: mv = move.make(pant, ptjk, wdjk, parms) omvs.append(mv) pant = ptjk wdjk = wd wdjk = 0 assert pant != None # Since there must have been at least one point. if len(omvs) == 0: return path.make_empty(pant) else: return path.from_moves(omvs) def concat(ophs, jumps, parms): assert type(ophs) is list or type(ophs) is tuple m = len(ophs) # Number of paths. assert m >= 1, "must have at least one path" p = pini(ophs[0]) # Initial point of final path. q = pfin(ophs[m-1]) # Final point of final path. pant = p omvs = [] for ophj in ophs: phj, drj = unpack(ophj) # For type checking. nj = nelems(ophj) pj = pini(ophj) qj = pfin(ophj) if pant != pj: # Needs a connector. Get one: if jumps or nj == 0 or len(omvs) == 0: # Insert a jump: cnj = move.make(pant, pj, 0, parms) else: # Use a link if geometrically reasonable: cnj = move.connector(omvs[-1], path.elem(ophj,0), parms) # Insert the connector: assert isinstance(cnj, move.Move) omvant = cnj omvs.append(cnj) pant = pj # Append the moves of {ophj}: for k in range(nj): omvk = elem(ophj, k) pk = move.pini(omvk) qk = move.pfin(omvk) assert pant == pk omvs.append(omvk) pant = qk assert pant == q return path.from_moves(omvs) def displace(oph, ang, v, parms): n = nelems(oph) if (n == 0): p = pini(oph) return empty(p) else: omvs = [] for k in range(n): omvk = elem(oph, k) omvs.append(move.displace(omvk, ang, v, parms)) return from_moves(omvs) def orient(oph, dr): ph1, dr1 = unpack(oph) return (ph1, (dr1 + dr) % 2) def rev(oph): ph, dr = unpack(oph) return (ph, 1-dr) def unpack(oph): # sys.stderr.write("oph = %s\n" % str(oph)) if isinstance(oph, path.Path): # sys.stderr.write("returning %s, 0\n" % str(oph)) return oph, 0 else: assert type(oph) is tuple assert len(oph) == 2 ph, dr = oph # sys.stderr.write("ph = %s dr = %s\n" % (str(ph), str(dr))) assert isinstance(ph, path.Path) assert dr == 0 or dr == 1 return ph, dr def extime(oph): ph, dr = unpack(oph) n = len(ph.omv) return 0 if n == 0 else ph.cumtex[n-1] def tini(oph, k): ph, dr = unpack(oph) n = len(ph.omv) assert k >= 0 and k <= n if k == 0: return 0 if k == n: return extime(oph) if dr == 0: # Native direction: return ph.cumtex[k-1] else: # Reverse direction return ph.cumtex[n-1] - ph.cumtex[n-1-k] def tfin(oph, k): return tini(oph, k+1) def plot_standard(c, oph, dp, layer, ctraces, waxes, axes, dots, arrows, matter): assert waxes != None and waxes > 0, "invalid waxes" # Get the {Path} object {ph} and the direction {dr} ({None} if un-oriented): ph, dr = unpack(oph) if layer == None: # Plot all four layers. lys = (range(4)) else: # Plot only the selected layer. assert type (layer) is int assert layer >= 0 and layer < 4 lys = (layer,) # Plot all moves, layer by layer: for ly in lys: for k in range(nelems(oph)): omvk = elem(oph, k) move.plot_standard(c, omvk, dp, ly, ctraces, waxes, axes, dots, arrows, matter) def plot_layer(c, oph, dp, jmp, clr, rtraces, waxes, dashed, wdots, szarrows): # Get the {Path} object {ph} and the direction {dr} ({None} if un-oriented): ph, dr = unpack(oph) if clr == None: return # Simplfications: if rtraces == None: waxes = 0 if waxes == None: waxes = 0 if wdots == None: wdots = 0 if szarrows == None: szarrows = 0 assert rtraces >= 0 and waxes >= 0 and wdots >= 0 and szarrows >= 0 for k in range(nelems(oph)): omvk = elem(oph, k) if move.is_jump(omvk) == jmp: wdk = rtraces*move.width(omvk) + waxes move.plot_layer(c, omvk, dp, clr, wdk, dashed, wdots, szarrows) def validate(oph): n = nelems(oph) ph, dr = unpack(oph) assert len(ph.omv) == n assert len(ph.cumtex) == n # sys.stderr.write("dr = %d cumtex = %s\n" % (dr, str(ph.cumtex))) # Validate the list of moves: pant = pini(oph) # Final point of previous move. tant = 0 # Cumulative execution time. for k in range(n): omvk = elem(oph, k) # sys.stderr.write("omvk = %s\n" % str(omvk)) mvk, drk = move.unpack(omvk) # Just to typecheck. # sys.stderr.write("mvk = %s drk = %s\n" % (str(mvk), str(drk))) mv_pini = move.pini(omvk) mv_pfin = move.pfin(omvk) # sys.stderr.write("pant = %s pini = %s\n" % (str(pant), str(mv_pini))) assert pant == mv_pini mv_tini = tini(oph, k) mv_tfin = tfin(oph, k) mv_tex = move.extime(omvk) # sys.stderr.write("k = %d tant = %12.8f tini = %12.8f tfin = %12.8f tex = %12.8f\n" % (k,tant,mv_tini,mv_tfin,mv_tex)) assert abs(tant - mv_tini) < 1.0e-8 assert abs((mv_tfin - mv_tini) - mv_tex) < 1.0e-8 pant = mv_pfin tant = mv_tfin assert pant == pfin(oph) assert abs(tant - extime(oph)) < 1.0e-8