# Some example paths for tests and illustrations. # Last edited on 2021-09-30 14:24:15 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 {k} 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 # 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) empty (zero moves) # PHS[3] 1 (1,3) (1,3) single trace of zero length # PHS[4] 0 (2,4) (2,4) empty, 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. # # The moves are as follows: # # alt=False alt=True # ----------------- ------------------- # k type pini pfin type pini pfin # -- ----- ----- ----- ----- ----- ----- # 0 ????? (?,?) (?,?) ????? (?,?) (?,?) # 1 ????? (?,?) (?,?) ????? (?,?) (?,?) # 2 ????? (?,?) (?,?) ????? (?,?) (?,?) # 3 ????? (?,?) (?,?) ????? (?,?) (?,?) # 4 ????? (?,?) (?,?) ????? (?,?) (?,?) # 5 ????? (?,?) (?,?) ????? (?,?) (?,?) # 6 ????? (?,?) (?,?) ????? (?,?) (?,?) # 7 ????? (?,?) (?,?) ????? (?,?) (?,?) # 8 ????? (?,?) (?,?) ????? (?,?) (?,?) # 9 ????? (?,?) (?,?) ????? (?,?) (?,?) # 10 ????? (?,?) (?,?) ????? (?,?) (?,?) # 11 ????? (?,?) (?,?) ????? (?,?) (?,?) 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) # MISCELLANEOUS CONTOUR PATHS FOR TESTING 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. # # Calls {path.compute_contour_nesting} on those paths to define the # {path.inner_contours} and {path.outer_contours} lists. 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 empty (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 empty. return path_example_IMP.ring_fill(Rin, Rex, dmin, dmax, step, zigzag, side, mp_fill, mp_jump)