(points:Sequence[Point23], 
                  offset:float, 
                  internal:bool=True,
                  closed=True)
return intersections
Given a set of points, return a set of points offset by `offset`, in the
    direction specified by `internal`. 

    NOTE: OpenSCAD has the native `offset()` function that generates offset 
    polygons nicely as well as doing fillets & rounds. If you just need a shape,
    prefer using the native `offset()`.  If you need the actual points for some
    purpose, use this function.
    See: https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Transformations#offset

    # NOTE: We accept Point2s or Point3s, but ignore all Z values and return Point2s

    What is internal or external is defined by by the direction of curvature
    between the first and second points; for non-convex shapes, we will return
    an incorrect (internal points are all external, or vice versa) if the first
    segment pair is concave. This could be mitigated with a point_is_in_polygon()
    function, but I haven't written that yet.
Given a set of points, return a set of points offset by `offset`, in the
    direction specified by `internal`.
Given a set of points return a set of points offset by offset in the direction specified by internal .
def offset_points(points:Sequence[Point23], 
                  offset:float, 
                  internal:bool=True,
                  closed=True) -> List[Point2]:
    """
    Given a set of points, return a set of points offset by `offset`, in the
    direction specified by `internal`. 

    NOTE: OpenSCAD has the native `offset()` function that generates offset 
    polygons nicely as well as doing fillets & rounds. If you just need a shape,
    prefer using the native `offset()`.  If you need the actual points for some
    purpose, use this function.
    See: https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Transformations#offset

    # NOTE: We accept Point2s or Point3s, but ignore all Z values and return Point2s

    What is internal or external is defined by by the direction of curvature
    between the first and second points; for non-convex shapes, we will return
    an incorrect (internal points are all external, or vice versa) if the first
    segment pair is concave. This could be mitigated with a point_is_in_polygon()
    function, but I haven't written that yet.
    """
    # Note that we could just call offset_point() repeatedly, but we'd do 
    # a lot of repeated calculations that way
    src_points = euclidify(points, Point2)
    if closed:
        src_points.append(src_points[0])

    vecs = vectors_between_points(src_points)
    direction = direction_of_bend(*src_points[:3])
    if not internal:
        direction = opposite_direction(direction)

    perp_vecs = list((perpendicular_vector(v, direction=direction, length=offset) for v in vecs))

    lines: List[Line2] = []
    for perp, a, b in zip(perp_vecs, src_points[:-1], src_points[1:]):
        lines.append(Line2(a+perp, b+perp))
    
    intersections = list((a.intersect(b) for a,b in zip(lines[:-1], lines[1:])))
    if closed:
        # First point is determined by intersection of first and last lines
        intersections.insert(0, lines[0].intersect(lines[-1]))
    else:
        # otherwise use first and last points in lines
        intersections.insert(0, lines[0].p)
        intersections.append(lines[-1].p + lines[-1].v)
    return intersections
def offset_points ( points : Sequence [ Point23 ] , offset : float , internal : bool = True , closed = True ) -> List [ Point2 ] : # Note that we could just call offset_point() repeatedly, but we'd do  # a lot of repeated calculations that way src_points = euclidify ( points , Point2 ) if closed : src_points . append ( src_points [ 0 ] ) vecs = vectors_between_points ( src_points ) direction = direction_of_bend ( * src_points [ : 3 ] ) if not internal : direction = opposite_direction ( direction ) perp_vecs = list ( ( perpendicular_vector ( v , direction = direction , length = offset ) for v in vecs ) ) lines : List [ Line2 ] = [ ] for perp , a , b in zip ( perp_vecs , src_points [ : - 1 ] , src_points [ 1 : ] ) : lines . append ( Line2 ( a + perp , b + perp ) ) intersections = list ( ( a . intersect ( b ) for a , b in zip ( lines [ : - 1 ] , lines [ 1 : ] ) ) ) if closed : # First point is determined by intersection of first and last lines intersections . insert ( 0 , lines [ 0 ] . intersect ( lines [ - 1 ] ) ) else : # otherwise use first and last points in lines intersections . insert ( 0 , lines [ 0 ] . p ) intersections . append ( lines [ - 1 ] . p + lines [ - 1 ] . v ) return intersections