# Some example paths for tests and illustrations. # Last edited on 2021-02-18 21:16:30 by jstolfi import example_path_IMP # MISCELLANEOUS TEST PATHS def simple(parms): # Creates a simple path with only a few moves. # # The traces will have nominal width {parms['solid_raster_width']}. # The lowest and leftmost move endpoint will be at {(1,1)}. return example_path_IMP.simple (parms) def scanline_fill(alt, wd, parms): # Returns two results: a filling path {ph} built from a small set of # of raster line elements of width {wd}, and a contact among adjacent # traces of {ph} that is espected to have the largest cooling time. # # If {alt} is {False}, uses the unidirectional scan-line order with # jumps. If {alt} is {True}, uses alternating order, with links where # possible. # # The traces will have nominal width {wd}. The lowest and leftmost # move endpoint will be at {(1,1)}. return example_path_IMP.scanline_fill(alt, wd, parms) def raster_rectangle(n, dp, xsz, ystep, wd, use_jumps, parms): # Returns a path that consists of {n} horizontal raster lines of length {xsz} # and nominal width {wd}. # # The axes of successive rasters will be {ystep} apartin {Y}. The # rasters will be ordered from bottom to top if {ystep} is positive, # from top to bottom {ystep} is negative. Either way, the bottom # raster will start at the point {dp}, and will be oriented from left # to right. # # The rasters will be connected by jumps if {use_jumps} is true; # otherwise they may connected by links (traces) if the geometry # allows it. (See {move.connector_must_be_jump}.) return example_path_IMP.raster_rectangle(n, dp, xsz, ystep, wd, use_jumps, parms) def circle(ctr, R, wd, nt, phase, parms): # 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 nominal width {wd}, # whose endpoints lie on that circle. return example_path_IMP.circle(ctr, R, wd, nt, phase, parms) def onion(ctr, Rc, wdc, Rf, wdf, phase, nt, parms): # Creates a path that is the concatenation of /elements/, specifically # a /contour/ followed by a zero or more /fillers/. # # The contour is a circular path of traces with nominal width {wdc}. # It is generated by {circle(ctr,Rc,wdc,nt,phase,parms)}, 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,parms)}, 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 the nominal width {wdf}. # # The procedure returns three results: the path itself, the radius # {Rin} of the inscribed circle, and the radius {Rot} 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 # {Rot} 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 (execpt for a small gap to account for extra # material at the caps). 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 {Rot} 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 parameters {Rc}, {wdc}, and {wdf} must be positive, and {nt} # must be at least 3. return example_path_IMP.onion(ctr, Rc, wdc, Rf, wdf, phase, nt, parms) def gear(ctr, Rin, Rot, nt, split, phase, parms): # Returns a path {ph} that is a gear-like path centered at {ctr} with # {nt} teeth extending from radius {Rin} to radius {Rot}, rotated by # {phase} radians. The traces will be assumed to have effective width # {strace = # # If {split} is true, the path is interrupted by skipping some # of the teeth. return example_path_IMP.gear(ctr, Rin, Rot, nt, split, phase, parms) def gearloose(R, zigzag, parms): # 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. return example_path_IMP.gearloose(R, zigzag, parms) # FILLING ELEMENTS def ring_raster(Rin, Rot, d, side, parms): # 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 {Rot}. # # 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, whose axes all lie on {L}. # # The raster line is assumed to have width {strace = # parms['solid_raster_width']}. 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 {Rot-strace/2} and # outside the circle with radius {Rin+strace/2}. Note that the radii # {Rin} and {Rot} 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 {Rot} is negative or # {Rot-Rin} is less than {strace), 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 example_path_IMP.ring_line_trace(Rin, Rot, d, parms) def ring_zigzag(Rin, Rot, d, step, phase, parms): # 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 {Rot}. # # 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 {step/2} from {L}, on each side. # However, the tips are cut off so that each tooth extends only # {step-strace} away from {L}, where {strace = # parms['solid_raster_width']}, and has a short section at the top that # is parallel to {L}. This 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 # {Rot-strace/2} and outside the circle with radius {Rin+strace/2}. Note # that the radii {Rin} and {Rot} must be adjusted by the caller to # account for the width of traces used to trace the two circles. Connectors # (jumps or short traces) are inserted to bridge two or more connected # parts of the clipped zigzag. # # If the resulting path would be empty (in particular, if {Rot} is # negative or {Rot-Rin} is less than {strace), returns {None}. return example_path_IMP.ring_zigzag(Rin, Rot, d, step, phase, parms) def ring_fill(Rin, Rot, dmin, dmax, step, zigzag, side, parms): # 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,Rot} (as # generated by {ring_raster}). If {Rin} is negative, the area to be # filled is assumed to be the just the circle with radius {Rot}. # # 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. # # All traces (filling or connectors) will be assumed to have effective # width {strace = parms['solid_raster_width']}. The {step} must be # {strace} 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 {Rot-strace/2} and # outside the circle with radius {Rin+strace/2}. Note that the radii # {Rin} and {Rot} 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*strace}, 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 example_path_IMP.ring_fill(Rin, Rot, dmin, dmax, step, zigzag, side, parms)