# Some example paths for tests and illustrations.
# Last edited on 2021-10-29 14:23:20 by stolfi

import path_example_IMP

# MISCELLANEOUS TEST PATHS

def raster_rectangle(plo, axis, n, alt, sz, step, mp_trace,mp_jump):
  # Returns a list {PHS} of two {Path} objects that consist of the same
  # {n} parallel and aligned raster lines traversed and connected in
  # two different ways. The rasters wil be {sz} mm long and spaced
  # {step} mm apart.  Also returns the list {TRS} of those raster traces,
  # and the two lists {LJS0,LJS1} of the connecting jumps or links.
  # 
  # If {axis} is 0 the rasters will be horizontal, and will be used in
  # the path from bottom to top. If {axis} is 1 the rasters will be
  # vertical, and will be used from left to right. In any case, the
  # raster endpoints will fit a rectangle with corners {plo} and {plo +
  # (sz,(n-1)*step)} or {plo + ((n-1)*step, sz)}, depending on {axis}.
  # 
  # If {alt} is false, all raster traces will be oriented in the same
  # direction, and will be connected by {n-1} jumps.
  #
  # In any case, the traces in the returned trace list {TSR} will all be
  # oriented in the same direction, and the first one will starts at
  # {plo}, as in path {PHS[0]}.
  #
  # if {alt} is true, the rasters will have alternating directions and
  # will be connected by {n-1} links (traces). All paths will start with
  # the trace that has {plo} as an endpoint; but path {PH[0]} will start
  # at {plo}, while path {PHS[1]} will start with the opposite end.
  # 
  # If {alt} is true, the returned lists {LJS0} and {LJS1} will contain
  # the connecting links as described in {move_example.rectangle_links}.
  # If {alt} is false, they will contain the connecting jumps as 
  # described by {move_example.rectangle_jumps}.
  #
  # The jumps and traces will have parameters {mp_jump} and {mp_trace},
  # respectively, which must be {Move_Parms} obects. If {alt} is true,
  # the {mp_jump} parameter may be {None}, and the links too wil use
  # {mp_trace}.
  return path_example_IMP.raster_rectangle(plo, axis, n, alt, sz, step, mp_trace,mp_jump)

def spiral_rectangle(pini, szx, szy, axis, mp_trace):
  # Returns a path that fills a rectangular area with a spiral pattern of alternating 
  # vertical and horizontal raster lines.  
  #
  # The area is the axis-aligned rectangle with opposite corners at
  # {pini} and {pini +(szx,szy)}. The parameters {szx} and/or {szy} may
  # be negative. The endpoints of the path traces will lie inside or on
  # the boundary of the rectangle.
  #
  # The path will start at {pini}, trace out the boundary of the
  # rectangle, and then fill the interior. The first trace will be
  # horizontal if {axis} is 0 or vertical is {axis} is 1. It will stop
  # when there is no more enough space to fit another trace.
  #
  # All traces will have the {Move_Parms} record {mp_trace}. The width
  # {abs(szx)} and height {abs(szy)} of the area must be at least equal to the width
  # of the traces in order for its boundary to be completely traced.
  return path_example_IMP.spiral_rectangle(pini, szx, szy, axis, mp_trace)

def circle(ctr, R, nt, phase, mp_trace, gap):
  # Returns a path {ph} that is an extruded circle with center {ctr} and
  # radius {R}, rotated {phase} radians. 
  #
  # More precisely, the path will consist of {nt} traces with parameters {mp_trace},
  # whose endpoints lie on that circle, each spanning a central angle of {2*pi/nt}
  # radians.  However, the first and last traces are slightly trimmed to account for
  # extra material deposited at those ends.
  return path_example_IMP.circle(ctr, R, nt, phase, mp_trace, gap)

def onion(ctr, Rc, mp_cont, Rf, mp_fill, phase, nt, mp_jump):
  # Creates a path that is the concatenation of /elements/, specifically
  # a /contour/ followed by a zero or more /fillers/, connected by jumps.
  #
  # The contour is a circular path of traces with paramters {mp_cont}.
  # It is generated by {circle(ctr,Rc,wdc,nt,phase,mp_cont)}, and is
  # actually a regular polygon with {nt} straight traces whose endpoints
  # lie on the circle with center {ctr} and radius {Rc}.
  #
  # Each filler {kph} is similarly a circular path generated by
  # {circle(ctr,Rk,wdf,ntk,phasek,mp_fill)}.  The radius {Rk} of this
  # circle is such that the filler just touches the previous element
  # (contour or filler). The number of sides {ntk} is {nt} or some
  # divisor of {nt}, chosen to ensure that the gaps between the two
  # elements are small compared to their nominal width.
  #
  # The first and last trace of each circle (contour or filling) are
  # slightly trimmed, to account for extra material at the caps.
  # If {nt} is large, the trimming may eliminate some whole traces.
  #
  # The procedure returns three results: the path itself, the radius
  # {Rin} of the inscribed circle, and the radius {Rex} of the exscribed
  # circle.
  #
  # If {Rf < Rc}, the fillers are nested inside the contour. Fillers are
  # added, from outer to inner, as long as the inradius {Rin} of the
  # innermost filler is not less than {Rf}.
  #
  # If {Rf > Rc}, the fillers are nested outside the contour. Fillers
  # are added, from inner to outer, as long as the exradius {Rex} or the
  # outermost filler is not greater than {Rf}.
  #
  # The contour begins and ends at a point that is {phase} radians CCW
  # from the {X}-axis. Each
  # filler is offset in phase relative to the previous element, in order
  # to avoid creating a weak seam line in the whole path.
  #
  # Note that the contour is always traced, even if it violates the
  # stopping conditions for the fillers.
  #
  # More precisely, {Rin} is the radius of largest circle with center
  # {ctr} that just touches the inner edges of the traces; and {Rex} is
  # the radius of the smallest circle with center {ctr} that just
  # touches the outer boudary of the traces. These parameters account
  # for the fact that the path is actually made of {nt} straight traces
  # with nominal width {wd} and round caps of radius {wd/2} at the ends.
  #
  # The parameter {Rc} must be positive, the parameters
  # {mp_cont,mp_fill,mp_jump} must be {Move_Parms} with proper widths,
  # and {nt} must be at least 3.
  return path_example_IMP.onion(ctr, Rc, mp_cont, Rf, mp_fill, phase, nt, mp_jump)
 
def gear(ctr, Rin, Rex, nt, split, phase, mp_trace, mp_jump):
  # Returns a path {ph} that is a gear-like path centered at {ctr} with
  # {nt} teeth extending from radius {Rin} to radius {Rex}, rotated by
  # {phase} radians. The jumps and traces will have paramters {mp_jump}
  # aand {mp_trace}, respectively.
  #
  # If {split} is true, the path is interrupted by skipping some of the
  # teeth.
  return path_example_IMP.gear(ctr, Rin, Rex, nt, split, phase, mp_trace, mp_jump)

def gearloose(R, zigzag, mp_cont, mp_fill, mp_jump):
  # Makes a medium-complexity test path with an
  # interrupted gear-like path of outer radius {R}, a filled
  # annulus, and a single dot at the center.
  # 
  # The {zigzag} parameter is a bool that decides wether the 
  # filling of the annulus is a plain solid raster fill (if false)
  # or a partial open pattern (if true).
  # 
  # The move parameters {mp_fill} are used for fillings and the single
  # dot, while {mp_cont} is used for contours and the gear-like path.
  return path_example_IMP.gearloose(R, zigzag, mp_cont, mp_fill, mp_jump)

# SAMPLE PATHS FOR THE UNIT TEST PROGRAMS

# In these procedures, parameters that begin with {mp_}
# should be {Move_Parms} objects.

def misc_A(mp_trace1, mp_trace2, mp_jump):
  # Returns a single path {ph} with three moves, being two traces with
  # parameters {mp_trace1} and {mp_trace2}, connected by a jump with
  # parameters {mp_jump}. 
  #
  # The moves {elem(ph,k)} are:
  #
  #  k  type   pini  pfin
  #  - -----  ----- -----
  #  0 trace1 (2,2) (3,3)
  #  1 jump   (3,3) (2,4)
  #  2 trace2 (2,4) (1,3)
  #
  return path_example_IMP.misc_A(mp_trace1, mp_trace2, mp_jump)

def misc_B(mp_trace, mp_jump):
  # Returns a single path with 12 moves, being 10 traces with 
  # parameters {mp_trace} and two jumps with parameters {mp_jump}.
  #
  # The moves {elem(ph,k)} are:
  #
  #  k  type   pini  pfin
  #  -- -----  ----- -----
  #   0 trace  (1,1) (1,4) vertical
  #   1 trace  (1,4) (2,4) horizontal
  #   2 jump   (2,4) (3,5) 
  #   3 trace  (3,5) (4,0) diagonal
  #   4 jump   (4,0) (5,1) 
  #   5 trace  (5,1) (7,1) horizontal
  #   6 trace  (7,1) (7,2) link vertical
  #   7 trace  (7,2) (6,2) horizontal
  #   8 trace  (6,2) (5,3) link diagonal
  #   9 trace  (5,3) (7,3) horizontal
  #  10 trace  (7,3) (7,4) link vertical
  #  11 trace  (7,4) (5,4) horizontal
  #
  return path_example_IMP.misc_B(mp_trace, mp_jump)

def misc_C(mp_trace, mp_link, mp_jump):
  # Returns a list {PHS} of five {Path} objects containing a total of
  # five moves, including traces with parameters {mp_trace}
  # and jumps with parameters {mp_jump}. 
  #
  # There will be a two-trace link path between every pair of distinct
  # endpoinst of distinct paths that are at most 2.5 mm apart.
  #
  # The paths are:
  # 
  #  Path   n  pini  pfin  obs
  #  ------ -  ----- ----- ----------------
  #  PHS[0] 3  (1,2) (4,2) two traces and one jump
  #  PHS[1] 1  (4,4) (3,3)
  #  PHS[2] 0  (2,4) (2,4) trivial (zero moves)
  #  PHS[3] 1  (1,3) (1,3) single trace of zero length
  #  PHS[4] 0  (2,4) (2,4) trivial, copy of {PHS2} but distinct
  #
  # where {n} is the number of moves. The moves {elem(PHS[i],k)} are
  # 
  #   Path   k type   pini  pfin  obs
  #   ------ - -----  ----- ----- ----------------
  #   PHS[0] 0 trace  (1,2) (2,1) 
  #   PHS[0] 1 jump   (2,1) (3,1) 
  #   PHS[0] 2 trace  (3,1) (4,2)
  #
  #   PHS[1] 0 trace  (4,4) (3,3) 
  #
  #   PHS[3] 0 trace  (1,3) (1,3) zero length
  #
  return path_example_IMP.misc_C(mp_trace, mp_link, mp_jump)

def misc_D(mp_trace, mp_jump):
  # Returns a list {OPHS} of six oriented paths created by
  # {move_example.misc_B} with parameters {mp_trace, mp_jump}. There are
  # a total of eight traces and five jumps. Some of the moves are used
  # in the reverse direction.
  #
  # Also resturns the lists {TRS} and {JMS}.
  #
  # The paths are
  # 
  #  Path    n  d  pini  pfin  obs
  #  ------- -  -  ----- ----- ----------------
  #  OPHS[0] 3  0  (1,1) (2,2) two adjacent rasters with a link on the right.
  #  OPHS[1] 3  0  (4,1) (5,2) two adjacent rasters with a link on the left.
  #  OPHS[2] 4  1  (1,1) (1,1) closed, two rasters and two links.
  #  OPHS[3] 3  0  (6,6) (4,3) two traces joined by a jump.
  #  OPHS[4] 3  0  (3,6) (5,3) same traces in opposite order with another jump.
  #  OPHS[5] 2  1  (0,3) (2,3) two traces as an inverted "V".
  #
  # The elements of the paths are
  #
  #   Path    k type    move  obs
  #   ------- - ----- ------- ----------------
  #   OPHS[0] 0 trace  TRS[0] 
  #   OPHS[0] 1 trace  TRS[1] 
  #   OPHS[0] 2 trace  TRS[2]  
  #
  #   OPHS[1] 0 trace ~TRS[0]
  #   OPHS[1] 1 trace ~TRS[3]
  #   OPHS[1] 2 trace ~TRS[2]
  #
  #   OPHS[2] 0 trace ~TRS[3]
  #   OPHS[2] 1 trace ~TRS[2]
  #   OPHS[2] 2 trace ~TRS[1]
  #   OPHS[2] 3 trace ~TRS[0]
  #
  #   OPHS[3] 0 trace  TRS[4]
  #   OPHS[3] 1 jump   JMS[0]
  #   OPHS[3] 2 trace  TRS[5]
  #
  #   OPHS[4] 0 trace  TRS[5]
  #   OPHS[4] 1 jump   JMS[4]
  #   OPHS[4] 2 trace  TRS[4]
  #
  #   OPHS[5] 0 trace ~TRS[7]
  #   OPHS[5] 1 trace ~TRS[6]
  #
  # where {~mv} means {move.rev(mv)}.
  return path_example_IMP.misc_D(mp_trace, mp_jump)

def misc_E(mp_trace, mp_jump):
  # Returns a list {OPHS} of five oriented paths with moves created by
  # {move_example.misc_C} with parameters {mp_trace, mp_jump}. There are
  # a total of ten traces and five jumps.  Some of the moves are
  # used in the reverse direction.
  #
  # Also resturns the lists {TRS} and {JMS}.
  #
  # Paths ({n} = number of moves):
  #
  #   k name n pini  pfin moves 
  #   - ---- - ----- ---- ---------------
  #   0  Pa  3 (1,3) (3,2) Ta0,Ja0,Ta1
  #   1  Pb  5 (5,1) (6,5) ~Tb0,Jb0,Tb1,Jd0,Tb2
  #   2 ~Pc  3 (@,1) (2,4) ~Tc1,~Jc0,~Tc0
  #   3  Pd  3 (2,7) (7,7) Td0,Jb1,Td1
  #   4  Pe  1 (3,8) (6,8) Te0
  # 
  # is {TRS[i]}, "J{i}" is {JMS[i]}, and {~mv} means {move.rev(mv)}.
  # The column "n" is the number of moves, and "d" is the path's 
  # orientation bit.
  # 
  return path_example_IMP.misc_E(mp_trace, mp_jump)

def misc_F(alt, mp_trace, mp_jump):
  # Returns a filling path {ph} built from a small set of
  # of raster line elements.
  #
  # The jumps and traces will have parameters {mp_jump} and
  # {mp_trace}, respectively, which must be {Move_Parms} obects.
  #
  # If {alt} is {False}, uses the unidirectional scan-line order with
  # jumps. If {alt} is {True}, uses alternating order, with links where
  # possible.
  return path_example_IMP.misc_F(alt, mp_trace, mp_jump)
 
def misc_G(mp_cont,mp_fill,mp_jump):
  # Returns a list {PHS} of three {Path} objects and two lists {TRS02,TRS1} of the
  # {Move} objects of the traces that appear in those paths. 
  #
  # The list {TRS} has no repetitions. Paths 
  # {PHS[0]} and {PHS[2]} use the traces {TRS02} in different 
  # orders and  orientations. Path {PHS[1]} uses the traces of {TRS1}
  return path_example_IMP.misc_G(mp_cont,mp_fill,mp_jump)

def misc_H(mp_trace):
  # Returns a list {OPHS} of four paths that connect corners of a CCW square {A} to 
  # corners of another CW square  {B}.  More specifically, path {OPHS[i]}
  # connects corner {i} of {A} to corner {i}  of {B}.  The paths
  # do not cross each other or the sides of the squares. 
  return path_example_IMP.misc_H(mp_trace)

def misc_J(mp_cont, mp_fill):
  # Returns a list {OPHS} of two paths, one closed with parameters {mp_cont}
  # and one open with parameters {mp_fill}. 
  return path_example_IMP.misc_J(mp_cont, mp_fill)

def misc_K(nmv,step,pert,mp_cont,mp_fill,mp_link,mp_jump):
  # Returns a path that is a square where each side has {nmv} moves of length
  # {step}. Endpoints that are not corners of the square are pertubed by
  # {±pert} in each coordinate. The moves have various parameter records:
  #   Side 0: all moves are {mp_fill}.
  #   Side 1: half the moves are {mp_fill}, half are {mp_cont}
  #   Side 2: moves are {mp_fill} except the one with index {nmv//2} is {m_jump}.
  #   Side 3: all moves are jumps with different parameters.
  # There will be two link paths attached to each end of {oph}.
  return path_example_IMP.misc_K(nmv,step,pert,mp_cont,mp_fill,mp_link,mp_jump)

# MISCELLANEOUS CONTOUR PATHS FOR TESTING

# Each procedure in this section returns a list of closed paths that
# follow the boundary of the slice.  Borders of islands are traced in CCW sense,]
# borders of holes in CW sense.  The nesting relations between those paths are computed
# by {path.compute_contour_nesting}

def contours_A(mp_cont):
  # Returns a list of 10 contour paths {A,B,C,D,E,F,G,H,I,J} with the containment 
  # structure {(A(C(F,G),D(H(I,J))),B(E))}.  Also returns a list with 
  # 10 lists of points that are the vertices of those contours.
  return path_example_IMP.contours_A(mp_cont)

def contours_B(mp_cont):
  # Returns a list of two paths that are concentric circles.
  # The traces will have parameters {mp_cont}.
  return path_example_IMP.contours_B(mp_cont)

# FILLING ELEMENTS
 
def ring_raster(Rin, Rex, d, side, mp_fill, mp_jump):
  # Returns a path {ph} consisting of collinear raster lines which
  # can be used to fill the ring (annulus) with center at the origin, inner
  # radius {Rin}, and outer radius {Rex}.
  #
  # Let {L} be the line parallel to the {X}-axis that passes through the
  # point {(0,d)}. The returned path {ph} will consist of one trace, or
  # two traces with an intervening jump with parameters {mp_jump}, whose
  # axes all lie on {L}.
  #
  # The raster line is assumed to have parameters {mp_fill}. The end
  # caps of the traces will just touch the circles' boundaries. That is,
  # the axes of the traces will be the parts of {L} inside the circle
  # with radius {Rex-wd/2} and outside the circle with radius
  # {Rin+wd/2}, where {wdf = move_parms.width(mp_fill}). Note that the
  # radii {Rin} and {Rex} must be adjusted by the caller to account for
  # the width of traces used in the two circles.
  #
  # If no such trace is possible (in particular, if {Rex} is negative or
  # {Rex-Rin} is less than {wdf), returns {None}.
  #
  # The traces are oriented and sorted left to right. If there are two
  # traces that satisfy those constraints, the resulting path will have
  # only the leftmost trace if {side} is negative, only the right trace if
  # {side} is positive, and both traces connected by a jump if {side} is
  # zero.
  return path_example_IMP.ring_line_trace(Rin, Rex, d, mp_fill, mp_jump)

def ring_zigzag(Rin, Rex, d, step, phase, mp_fill, mp_jump):
  # Creates a zigzag path {ph}, clipped so that it can be used to fill
  # the ring (annulus) with center at the origin, inner radius {Rin}, and
  # outer radius {Rex}.
  #
  # Let {L} be the line parallel to the {X}-axis that passes through the
  # point {(0,d)}. Conceptually, creates an infinite zigzag path with
  # {L} as the mean axis. Ideally, the teeth of the zigzag have 60
  # degree angles and tips that lie at distance {wdf/2} from {L}, on
  # each side, where {wdf = move_parms.width(mp_fill}). However, the
  # tips are cut off so that each tooth extends only {step-wdf} away
  # from {L}, and has a short section at the top that is parallel to
  # {L}. This infinite path is generally oriented from left to right.
  #
  # The {phase} parameter defines the horizontal alignment of this
  # infinite zizgag. If {phase} is 0, one of the ideal teeth tips will
  # be placed at coordinates {(0,d+step/2)}. A positive {phase} will
  # shift the path by that multiple of the teeth spacing. Thus
  # {phase=0.5} will have a tooth tip at {(0,d-step/2)}, and {phase=1}
  # is the same as {phase=0}.
  #
  # This conceptually infinite zig-zag path is then clipped so as to fit
  # inside the ring. The end caps of the traces will just touch the
  # circles' boundaries. That is, the axes of the traces will be the
  # parts of the zigzag segments that lie inside the circle with radius
  # {Rex-wdf/2} and outside the circle with radius {Rin+strace/2}. Note
  # that the radii {Rin} and {Rex} must be adjusted by the caller to
  # account for the width of traces used to trace the two circles.
  # Connectors (jumps with parameters {mp_jump} or links with parameters
  # {mp_fill}) are inserted to bridge two or more connected parts of the
  # clipped zigzag.
  #
  # If the resulting path would be trivial (in particular, if {Rex} is
  # negative or {Rex-Rin} is less than {strace), returns {None}.
  return path_example_IMP.ring_zigzag(Rin, Rex, d, step, phase, mp_fill, mp_jump)

def ring_fill(Rin, Rex, dmin, dmax, step, zigzag, side, mp_fill, mp_jump):
  # Returns a path {ph} that consists of raster or zigzag lines that lie
  # inside of the annulus with center at the origin and radii {Rin,Rex} (as
  # generated by {ring_raster}). If {Rin} is negative, the area to be
  # filled is assumed to be the just the circle with radius {Rex}. 
  #
  # The axes of the raster lines or the medial lines of the zigzags will
  # lie on a set of horizontal scan lines at {Y} coordinates {k*step} for
  # integer {k}. Only considers scan lines whose distances from {ctr} lie
  # in the interval {[ dmin _ dmax]}. Takes the signs of {dmin} and {dmax}
  # into account.
  #
  # The traces and jumps generated for each {k} will be a single subpath
  # of {ph}, denoted {el[k]} and called a /filling element/. Its traces
  # will be oriented and sorted in the {+X} or {-X} direction, depending
  # on whether {k} is even or odd, respectvely. Gaps between these
  # traces will be joined by /connectors/ that may be jumps or traces,
  # depending on geometric conditions.
  #
  # The jumps will have parameters {mp_jump}. All traces (fillings or
  # connectors) will have parameters {mp_fill} The {step} must be {wdf =
  # move_parms(mp_fill)} or greater. The end caps of the clipped traces
  # will just touch the circles' boundaries. That is, the axes of the
  # traces will be clipped to the region inside the circle with radius
  # {Rex-wdf/2} and outside the circle with radius {Rin+wdf/2}. Note
  # that the radii {Rin} and {Rex} must be adjusted by the caller to
  # account for the width of traces used to create the circles.
  #
  # If {zigzag} is false, or {step} is {2*wdf}, all elements will be
  # raster lines. If there are two traces on the same scan line, the
  # resulting path will have only the leftmost trace if {side} is
  # negative, only the rightmost trace if {side} is positive, and both
  # traces connected by a jump if {side} is zero.
  #
  # If {zigzag} is true, there will be normal rasters only on
  # even-numbered scanlines (as created by {ring_raster}), and these will
  # be spaced {2*step} apart. The odd-numbered paths will be zigzags with
  # 60 degrees angles that (as in {ring_zigzag}) touch the adjacent
  # rasters or the ring contours.  The {side} flag will be ignored, so that
  # the fill will span both sides of the inner ring in any case.
  # The {phase} of the zigzag elements will alternate between 0 and 0.5,
  # being 0 for {el[1]}. 
  #
  # Returns {None} if the resulting path would be trivial.
  return path_example_IMP.ring_fill(Rin, Rex, dmin, dmax, step, zigzag, side, mp_fill, mp_jump)
