Esempio n. 1
0
def inflateDeflatePath(path, deflating, hDistance, vDistance=None, debugMode=False):
    """
    Edge case: What happens if we inflate an oval curve beyond it's center?
    Edge case: What happens if segment has a start or end vector of zero length?
        Should be fine.

    """

    def isEmptySegment(segment):
        return segment.startPoint() == segment.endPoint()

    if vDistance is None:
        vDistance = hDistance

    # TODO: We don't want to do this, right?
    #    paths = [orientClosedPathClockwise(path) for path in paths]

    path = path.removeEmptySegments()

    TIME_INFLATE_DEFLATE_PATHS = False
    #    debugMode = True

    if TIME_INFLATE_DEFLATE_PATHS:
        import time

        time0 = time.time()

    if deflating:
        path = path.reverse()

    #    raise TFSContoursException('argh', [path,])

    if debugMode:
        print "inflateDeflatePaths inputs"
        for index, segment in enumerate(path):
            print "\t", "segment", index, segment.description()
        print

    offsetSegments = []
    for index, segment in enumerate(path):
        newSegment = inflateSegmentLeft(segment, hDistance, vDistance)
        if newSegment is None:
            """
            Inflating a segment can result in an empty or otherwise invalid segment.
            In fact, this will happen often since we'll be deflating previously
            inflated rounding curves.
            That's fine; ignore them.
            """
            offsetSegments.append(None)
            continue

        if debugMode:
            print "inflating segment"
            print "\t", "from", index, segment.description()
            print "\t", "to", index, newSegment.description()

        #        newSegment.srcSegment = segment
        offsetSegments.append(newSegment)

    #    if debugMode:
    #        raise TFSContoursException('argh', [TFSPath(False, segment) for segment in offsetSegments if segment is not None])

    del segment

    newSegments = []
    #    lastSegment = offsetSegments[-1]
    lastSegmentInvalid = False
    #    for index, segment in enumerate(offsetSegments):
    for index in xrange(len(path)):
        offsetSegment = offsetSegments[index]
        lastOffsetSegment = offsetSegments[(index + len(path) - 1) % len(path)]
        srcSegment = path[index]
        lastSrcSegment = path[(index + len(path) - 1) % len(path)]

        if debugMode:
            print "checking join"
            print "\t", "offsetSegment", index, offsetSegment.description() if offsetSegment is not None else ""
            print "\t", "lastOffsetSegment", index, lastOffsetSegment.description() if lastOffsetSegment is not None else ""
            print "\t", "srcSegment", index, srcSegment.description() if srcSegment is not None else ""
            print "\t", "srcSegment", index, srcSegment.description() if srcSegment is not None else ""
        #            print '\t', 'to', index, segment.description()

        if (
            (offsetSegment is not None)
            and (lastOffsetSegment is not None)
            and (offsetSegment.startPoint() == lastOffsetSegment.endPoint())
        ):
            """
            No join necessary if inflated segments align perfectly.
            """
            if debugMode:
                print "No join necessary (segments aligned)"
        else:
            lastTangent = lastSrcSegment.endTangent()
            nextTangent = srcSegment.startTangent()
            lastAngle = lastTangent.atan2()
            nextAngle = nextTangent.atan2()
            angleDiff = normalizeRadiansDiff(nextAngle - lastAngle)
            if debugMode:
                print "lastTangent", lastTangent
                print "nextTangent", nextTangent
                print "lastAngle", lastAngle
                print "nextAngle", nextAngle
                print "angleDiff", angleDiff

            if angleDiff < 0 or angleDiff == math.pi:
                if debugMode:
                    print "rounding join"

                #                if lastSegmentInvalid and not deflating:
                if lastSegmentInvalid:
                    """
                    No join necessary if last segment was invalid.
                    """
                    if debugMode:
                        print "No join necessary (last segment invalid)"
                else:
                    roundingCenter = srcSegment.startPoint()
                    roundingc = TFSOval(roundingCenter, hDistance, vDistance)
                    #                    roundingStartAngle = offsetSegment.startPoint().minus(roundingCenter).atan2()
                    #                    roundingEndAngle = lastOffsetSegment.endPoint().minus(roundingCenter).atan2()
                    roundingStartAngle = srcSegment.startTangent().atan2() + math.pi * 0.5
                    roundingEndAngle = lastSrcSegment.endTangent().atan2() + math.pi * 0.5

                    if debugMode:
                        #                        print 'angleDiff', angleDiff
                        #                        print 'segment', segment.description()
                        #                        print 'lastSegment', lastSegment.description()
                        print "roundingCenter", roundingCenter
                        print "roundingStartAngle", roundingStartAngle
                        print "roundingEndAngle", roundingEndAngle

                    rounding = roundingc.createArc(roundingStartAngle, roundingEndAngle).reverse()
                    # Make sure the rounding endpoints exactly align.
                    if lastOffsetSegment is not None:
                        rounding[0].setStartPoint(lastOffsetSegment.endPoint())
                    if offsetSegment is not None:
                        rounding[-1].setEndPoint(offsetSegment.startPoint())
                    #                    if debugMode:
                    #                        rounding = openPathWithPoints(rounding.startPoint(), roundingCenter, rounding.endPoint())
                    if debugMode:
                        debugPath("rounding", rounding)

                    newSegments.extend(rounding.segments)
                    if debugMode:
                        print "inserting join"
                        for value in rounding.segments:
                            print "\t", "*", index, value.description()
            else:
                if debugMode:
                    print "cross join"
                if (offsetSegment is not None) and (lastOffsetSegment is not None):
                    joinTangent0 = offsetSegment.startPoint().minus(lastOffsetSegment.endPoint())
                    joinTangent1 = offsetSegment.endPoint().minus(lastOffsetSegment.endPoint())
                    joinAffinity0 = joinTangent0.dotProduct(nextTangent)
                    joinAffinity1 = joinTangent1.dotProduct(nextTangent)
                    #                if debugMode:
                    #            print 'nextTangent', nextTangent
                    #            print 'joinTangent0', joinTangent0
                    #            print 'joinTangent1', joinTangent1
                    #            print 'joinAffinity0', joinAffinity0
                    #            print 'joinAffinity1', joinAffinity1
                    if (joinAffinity0 < 0) and (joinAffinity1 < 0):
                        if debugMode:
                            print "invalid segment"
                        #                        lastSegment = segment
                        lastSegmentInvalid = True
                        continue

        if offsetSegment is not None:
            newSegments.append(offsetSegment)
        #        lastSegment = segment
        lastSegmentInvalid = False

    #    if debugMode:
    #        raise TFSContoursException('argh', [TFSPath(False, segment) for segment in newSegments])

    newSegments = [segment.roundWithDefaultPrecision() for segment in newSegments if not isEmptySegment(segment)]
    newPaths = [TFSPath(False, segment) for segment in newSegments]

    if debugMode:
        debugPaths("newPaths", newPaths)

    #    newPath = concatenatePath(True, *newSegments)

    #    raise TFSContoursException('argh', newPaths)

    if TIME_INFLATE_DEFLATE_PATHS:
        time1 = time.time()
        print "\t", "TFSSilhouette.deflatePaths.core", time1 - time0

    #    unTesselatedPaths = newPaths
    #    if debugMode:
    #        raise TFSContoursException('argh', unTesselatedPaths)

    if TIME_INFLATE_DEFLATE_PATHS:
        time0 = time.time()
    #    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, ignoreStrayEdges=deflating)
    #    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, ignoreStrayEdges=deflating, debugMode=debugMode)
    newPaths, intersections = FTesselation().tesselateContours(
        newPaths, reorientPaths=False, ignoreStrayEdges=True, debugMode=debugMode
    )
    #    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, ignoreStrayEdges=True, debugMode=debugMode)
    #    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, debugMode=True)
    if TIME_INFLATE_DEFLATE_PATHS:
        time1 = time.time()
        print "\t", "TFSSilhouette.deflatePaths.tesselateContours", time1 - time0

    #    raise TFSContoursException('argh', newPaths)

    if deflating:
        newPaths = [path.reverse() for path in newPaths]

    if TIME_INFLATE_DEFLATE_PATHS:
        time1 = time.time()
        print "\t", "TFSSilhouette.deflatePaths.cleanup", time1 - time0

    return newPaths
Esempio n. 2
0
def inflateDeflatePath(path,
                       deflating,
                       hDistance,
                       vDistance=None,
                       debugMode=False):
    '''
    Edge case: What happens if we inflate an oval curve beyond it's center?
    Edge case: What happens if segment has a start or end vector of zero length?
        Should be fine.

    '''
    def isEmptySegment(segment):
        return segment.startPoint() == segment.endPoint()

    if vDistance is None:
        vDistance = hDistance

    # TODO: We don't want to do this, right?
#    paths = [orientClosedPathClockwise(path) for path in paths]

    path = path.removeEmptySegments()

    TIME_INFLATE_DEFLATE_PATHS = False
    #    debugMode = True

    if TIME_INFLATE_DEFLATE_PATHS:
        import time
        time0 = time.time()

    if deflating:
        path = path.reverse()

#    raise TFSContoursException('argh', [path,])

    if debugMode:
        print 'inflateDeflatePaths inputs'
        for index, segment in enumerate(path):
            print '\t', 'segment', index, segment.description()
        print

    offsetSegments = []
    for index, segment in enumerate(path):
        newSegment = inflateSegmentLeft(segment, hDistance, vDistance)
        if newSegment is None:
            '''
            Inflating a segment can result in an empty or otherwise invalid segment.
            In fact, this will happen often since we'll be deflating previously
            inflated rounding curves.
            That's fine; ignore them.
            '''
            offsetSegments.append(None)
            continue

        if debugMode:
            print 'inflating segment'
            print '\t', 'from', index, segment.description()
            print '\t', 'to', index, newSegment.description()

#        newSegment.srcSegment = segment
        offsetSegments.append(newSegment)

#    if debugMode:
#        raise TFSContoursException('argh', [TFSPath(False, segment) for segment in offsetSegments if segment is not None])

    del segment

    newSegments = []
    #    lastSegment = offsetSegments[-1]
    lastSegmentInvalid = False
    #    for index, segment in enumerate(offsetSegments):
    for index in xrange(len(path)):
        offsetSegment = offsetSegments[index]
        lastOffsetSegment = offsetSegments[(index + len(path) - 1) % len(path)]
        srcSegment = path[index]
        lastSrcSegment = path[(index + len(path) - 1) % len(path)]

        if debugMode:
            print 'checking join'
            print '\t', 'offsetSegment', index, offsetSegment.description(
            ) if offsetSegment is not None else ''
            print '\t', 'lastOffsetSegment', index, lastOffsetSegment.description(
            ) if lastOffsetSegment is not None else ''
            print '\t', 'srcSegment', index, srcSegment.description(
            ) if srcSegment is not None else ''
            print '\t', 'srcSegment', index, srcSegment.description(
            ) if srcSegment is not None else ''
#            print '\t', 'to', index, segment.description()

        if ((offsetSegment is not None) and (lastOffsetSegment is not None) and
            (offsetSegment.startPoint() == lastOffsetSegment.endPoint())):
            '''
            No join necessary if inflated segments align perfectly.
            '''
            if debugMode:
                print 'No join necessary (segments aligned)'
        else:
            lastTangent = lastSrcSegment.endTangent()
            nextTangent = srcSegment.startTangent()
            lastAngle = lastTangent.atan2()
            nextAngle = nextTangent.atan2()
            angleDiff = normalizeRadiansDiff(nextAngle - lastAngle)
            if debugMode:
                print 'lastTangent', lastTangent
                print 'nextTangent', nextTangent
                print 'lastAngle', lastAngle
                print 'nextAngle', nextAngle
                print 'angleDiff', angleDiff

            if angleDiff < 0 or angleDiff == math.pi:
                if debugMode:
                    print 'rounding join'

#                if lastSegmentInvalid and not deflating:
                if lastSegmentInvalid:
                    '''
                    No join necessary if last segment was invalid.
                    '''
                    if debugMode:
                        print 'No join necessary (last segment invalid)'
                else:
                    roundingCenter = srcSegment.startPoint()
                    roundingc = TFSOval(roundingCenter, hDistance, vDistance)
                    #                    roundingStartAngle = offsetSegment.startPoint().minus(roundingCenter).atan2()
                    #                    roundingEndAngle = lastOffsetSegment.endPoint().minus(roundingCenter).atan2()
                    roundingStartAngle = srcSegment.startTangent().atan2(
                    ) + math.pi * 0.5
                    roundingEndAngle = lastSrcSegment.endTangent().atan2(
                    ) + math.pi * 0.5

                    if debugMode:
                        #                        print 'angleDiff', angleDiff
                        #                        print 'segment', segment.description()
                        #                        print 'lastSegment', lastSegment.description()
                        print 'roundingCenter', roundingCenter
                        print 'roundingStartAngle', roundingStartAngle
                        print 'roundingEndAngle', roundingEndAngle

                    rounding = roundingc.createArc(roundingStartAngle,
                                                   roundingEndAngle).reverse()
                    # Make sure the rounding endpoints exactly align.
                    if lastOffsetSegment is not None:
                        rounding[0].setStartPoint(lastOffsetSegment.endPoint())
                    if offsetSegment is not None:
                        rounding[-1].setEndPoint(offsetSegment.startPoint())
#                    if debugMode:
#                        rounding = openPathWithPoints(rounding.startPoint(), roundingCenter, rounding.endPoint())
                    if debugMode:
                        debugPath('rounding', rounding)

                    newSegments.extend(rounding.segments)
                    if debugMode:
                        print 'inserting join'
                        for value in rounding.segments:
                            print '\t', '*', index, value.description()
            else:
                if debugMode:
                    print 'cross join'
                if ((offsetSegment is not None)
                        and (lastOffsetSegment is not None)):
                    joinTangent0 = offsetSegment.startPoint().minus(
                        lastOffsetSegment.endPoint())
                    joinTangent1 = offsetSegment.endPoint().minus(
                        lastOffsetSegment.endPoint())
                    joinAffinity0 = joinTangent0.dotProduct(nextTangent)
                    joinAffinity1 = joinTangent1.dotProduct(nextTangent)
                    #                if debugMode:
                    #            print 'nextTangent', nextTangent
                    #            print 'joinTangent0', joinTangent0
                    #            print 'joinTangent1', joinTangent1
                    #            print 'joinAffinity0', joinAffinity0
                    #            print 'joinAffinity1', joinAffinity1
                    if (joinAffinity0 < 0) and (joinAffinity1 < 0):
                        if debugMode:
                            print 'invalid segment'
#                        lastSegment = segment
                        lastSegmentInvalid = True
                        continue

        if offsetSegment is not None:
            newSegments.append(offsetSegment)
#        lastSegment = segment
        lastSegmentInvalid = False

#    if debugMode:
#        raise TFSContoursException('argh', [TFSPath(False, segment) for segment in newSegments])

    newSegments = [
        segment.roundWithDefaultPrecision() for segment in newSegments
        if not isEmptySegment(segment)
    ]
    newPaths = [TFSPath(False, segment) for segment in newSegments]

    if debugMode:
        debugPaths('newPaths', newPaths)

#    newPath = concatenatePath(True, *newSegments)

#    raise TFSContoursException('argh', newPaths)

    if TIME_INFLATE_DEFLATE_PATHS:
        time1 = time.time()
        print '\t', 'TFSSilhouette.deflatePaths.core', time1 - time0

#    unTesselatedPaths = newPaths
#    if debugMode:
#        raise TFSContoursException('argh', unTesselatedPaths)

    if TIME_INFLATE_DEFLATE_PATHS:
        time0 = time.time()
#    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, ignoreStrayEdges=deflating)
#    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, ignoreStrayEdges=deflating, debugMode=debugMode)
    newPaths, intersections = FTesselation().tesselateContours(
        newPaths,
        reorientPaths=False,
        ignoreStrayEdges=True,
        debugMode=debugMode)
    #    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, ignoreStrayEdges=True, debugMode=debugMode)
    #    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, debugMode=True)
    if TIME_INFLATE_DEFLATE_PATHS:
        time1 = time.time()
        print '\t', 'TFSSilhouette.deflatePaths.tesselateContours', time1 - time0


#    raise TFSContoursException('argh', newPaths)

    if deflating:
        newPaths = [path.reverse() for path in newPaths]

    if TIME_INFLATE_DEFLATE_PATHS:
        time1 = time.time()
        print '\t', 'TFSSilhouette.deflatePaths.cleanup', time1 - time0

    return newPaths
Esempio n. 3
0
def _flatePathLeft(path, hDistance, vDistance=None, debugMode=False):
    """
    Input paths may be clockwise (fill) or counter-clockwise (hole) if we are inflating.
    And they are reversed if we are deflating.

    Edge case: What happens if we inflate an oval curve beyond it's center?
    Edge case: What happens if segment has a start or end vector of zero length?
        Should be fine.
    """

    for segment in path:
        if segment.startVector().length() == 0 or segment.endVector().length() == 0:
            raise Exception("Cannot flate a segment without valid tangents: " + segment.description())

    if vDistance is None:
        vDistance = hDistance
    if hDistance <= 0 or vDistance <= 0:
        raise Exception("Invalid flate distances, hDistance: %f, vDistance: %f " % (hDistance, vDistance))

    TIME_INFLATE_DEFLATE_PATHS = False
    #    debugMode = True

    if TIME_INFLATE_DEFLATE_PATHS:
        import time

        time0 = time.time()

    #    raise TFSContoursException('argh', [path,])

    if debugMode:
        print "flatePathLeft input"
        for index, segment in enumerate(path):
            print "\t", "segment", index, segment.description()
        print

    """
    Phase 1: Naively "flate" segments left.
    """
    offsetSegments = []
    for index, segment in enumerate(path):
        newSegment = _flateSegmentLeft(segment, hDistance, vDistance)
        """
        Inflating a segment can result in an empty or otherwise invalid segment.
        In fact, this will happen often since we'll be deflating previously
        inflated rounding curves.
        That's fine; ignore them.
        """
        if newSegment is not None:
            if debugMode:
                print "inflating segment"
                print "\t", "from", index, segment.description()
                print "\t", "to", index, newSegment.description()

        offsetSegments.append(newSegment)
    del segment

    #    print 'hDistance', hDistance
    #    print 'vDistance', vDistance

    debugSegments("offsetSegments", offsetSegments)

    #    if debugMode:
    #        raise TFSContoursException('argh', [TFSPath(False, segment) for segment in offsetSegments if segment is not None])

    """
    Phase 2: Add rounding segments as necessary.
    """
    roundingPaths = [None] * len(path)
    lastSegmentInvalid = False
    #    for index, segment in enumerate(offsetSegments):
    for index, srcSegment in enumerate(path):
        lastSrcSegment = path[(index + len(path) - 1) % len(path)]
        offsetSegment = offsetSegments[index]
        lastOffsetSegment = offsetSegments[(index + len(path) - 1) % len(path)]

        if debugMode:
            print "checking join"
            print "\t", "offsetSegment", index, offsetSegment.description() if offsetSegment is not None else ""
            print "\t", "lastOffsetSegment", index, lastOffsetSegment.description() if lastOffsetSegment is not None else ""
            print "\t", "srcSegment", index, srcSegment.description() if srcSegment is not None else ""
            print "\t", "srcSegment", index, srcSegment.description() if srcSegment is not None else ""
        #            print '\t', 'to', index, segment.description()

        if (
            (offsetSegment is not None)
            and (lastOffsetSegment is not None)
            and offsetSegment.startPoint().roundedEquals(lastOffsetSegment.endPoint())
        ):
            #            (offsetSegment.startPoint() == lastOffsetSegment.endPoint())):
            """
            No join necessary if inflated segments that align perfectly.
            """
            if debugMode:
                print "No join necessary (segments aligned)"

            """
            Heal the join.
            """
            endpoint = offsetSegment.startPoint().midpoint(lastOffsetSegment.endPoint())
            offsetSegment.setStartPoint(endpoint)
            lastOffsetSegment.setEndPoint(endpoint)

            continue

        lastTangent = lastSrcSegment.endTangent()
        nextTangent = srcSegment.startTangent()
        lastAngle = lastTangent.atan2()
        nextAngle = nextTangent.atan2()
        angleDiff = normalizeRadiansDiff(nextAngle - lastAngle)
        if debugMode:
            print "lastTangent", lastTangent
            print "nextTangent", nextTangent
            print "lastAngle", lastAngle
            print "nextAngle", nextAngle
            print "angleDiff", angleDiff

        if angleDiff < 0 or angleDiff == math.pi:
            if debugMode:
                print "rounding join"

            #                if lastSegmentInvalid and not deflating:
            if lastSegmentInvalid:
                """
                No join necessary if last segment was invalid.
                """
                if debugMode:
                    print "No join necessary (last segment invalid)"
            else:
                roundingCenter = srcSegment.startPoint()
                roundingc = TFSOval(roundingCenter, hDistance, vDistance)
                #                    roundingStartAngle = offsetSegment.startPoint().minus(roundingCenter).atan2()
                #                    roundingEndAngle = lastOffsetSegment.endPoint().minus(roundingCenter).atan2()
                roundingStartAngle = srcSegment.startTangent().atan2() + math.pi * 0.5
                roundingEndAngle = lastSrcSegment.endTangent().atan2() + math.pi * 0.5

                if debugMode:
                    #                        print 'angleDiff', angleDiff
                    #                        print 'segment', segment.description()
                    #                        print 'lastSegment', lastSegment.description()
                    print "roundingCenter", roundingCenter
                    print "roundingStartAngle", roundingStartAngle
                    print "roundingEndAngle", roundingEndAngle

                try:
                    roundingPath = roundingc.createArc(roundingStartAngle, roundingEndAngle).reverse()
                except TFSValidationException, e:
                    """
                    If its too short to round, just use a straight segment.
                    """
                    continue

                # Make sure the rounding endpoints exactly align.
                if lastOffsetSegment is not None:
                    roundingPath[0].setStartPoint(lastOffsetSegment.endPoint())
                if offsetSegment is not None:
                    roundingPath[-1].setEndPoint(offsetSegment.startPoint())
                #                    if debugMode:
                #                        rounding = openPathWithPoints(rounding.startPoint(), roundingCenter, rounding.endPoint())
                if debugMode:
                    debugPath("roundingPath", roundingPath)

                roundingPaths[index] = roundingPath
                if debugMode:
                    print "inserting join"
                    for value in roundingPath.segments:
                        print "\t", "*", index, value.description()
        else:
            if debugMode:
                print "cross join"
            if (offsetSegment is not None) and (lastOffsetSegment is not None):
                joinTangent0 = offsetSegment.startPoint().minus(lastOffsetSegment.endPoint())
                joinTangent1 = offsetSegment.endPoint().minus(lastOffsetSegment.endPoint())
                joinAffinity0 = joinTangent0.dotProduct(nextTangent)
                joinAffinity1 = joinTangent1.dotProduct(nextTangent)
                #                if debugMode:
                #            print 'nextTangent', nextTangent
                #            print 'joinTangent0', joinTangent0
                #            print 'joinTangent1', joinTangent1
                #            print 'joinAffinity0', joinAffinity0
                #            print 'joinAffinity1', joinAffinity1
                if (joinAffinity0 < 0) and (joinAffinity1 < 0):
                    if debugMode:
                        print "invalid segment"
                    #                        lastSegment = segment
                    lastSegmentInvalid = True
                    continue

        lastSegmentInvalid = False
Esempio n. 4
0
def _flatePathLeft(path, hDistance, vDistance=None, debugMode=False):
    '''
    Input paths may be clockwise (fill) or counter-clockwise (hole) if we are inflating.
    And they are reversed if we are deflating.

    Edge case: What happens if we inflate an oval curve beyond it's center?
    Edge case: What happens if segment has a start or end vector of zero length?
        Should be fine.
    '''

    for segment in path:
        if (segment.startVector().length() == 0
                or segment.endVector().length() == 0):
            raise Exception('Cannot flate a segment without valid tangents: ' +
                            segment.description())

    if vDistance is None:
        vDistance = hDistance
    if hDistance <= 0 or vDistance <= 0:
        raise Exception(
            'Invalid flate distances, hDistance: %f, vDistance: %f ' % (
                hDistance,
                vDistance,
            ))

    TIME_INFLATE_DEFLATE_PATHS = False
    #    debugMode = True

    if TIME_INFLATE_DEFLATE_PATHS:
        import time
        time0 = time.time()

#    raise TFSContoursException('argh', [path,])

    if debugMode:
        print('flatePathLeft input')
        for index, segment in enumerate(path):
            print('\t', 'segment', index, segment.description())
        print()
    '''
    Phase 1: Naively "flate" segments left.
    '''
    offsetSegments = []
    for index, segment in enumerate(path):
        newSegment = _flateSegmentLeft(segment, hDistance, vDistance)
        '''
        Inflating a segment can result in an empty or otherwise invalid segment.
        In fact, this will happen often since we'll be deflating previously
        inflated rounding curves.
        That's fine; ignore them.
        '''
        if newSegment is not None:
            if debugMode:
                print('inflating segment')
                print('\t', 'from', index, segment.description())
                print('\t', 'to', index, newSegment.description())

        offsetSegments.append(newSegment)
    del segment

    #    print 'hDistance', hDistance
    #    print 'vDistance', vDistance

    debugSegments('offsetSegments', offsetSegments)

    #    if debugMode:
    #        raise TFSContoursException('argh', [TFSPath(False, segment) for segment in offsetSegments if segment is not None])
    '''
    Phase 2: Add rounding segments as necessary.
    '''
    roundingPaths = [
        None,
    ] * len(path)
    lastSegmentInvalid = False
    #    for index, segment in enumerate(offsetSegments):
    for index, srcSegment in enumerate(path):
        lastSrcSegment = path[(index + len(path) - 1) % len(path)]
        offsetSegment = offsetSegments[index]
        lastOffsetSegment = offsetSegments[(index + len(path) - 1) % len(path)]

        if debugMode:
            print('checking join')
            print(
                '\t', 'offsetSegment', index,
                offsetSegment.description()
                if offsetSegment is not None else '')
            print(
                '\t', 'lastOffsetSegment', index,
                lastOffsetSegment.description()
                if lastOffsetSegment is not None else '')
            print('\t', 'srcSegment', index,
                  srcSegment.description() if srcSegment is not None else '')
            print('\t', 'srcSegment', index,
                  srcSegment.description() if srcSegment is not None else '')
#            print '\t', 'to', index, segment.description()

        if ((offsetSegment is not None) and (lastOffsetSegment is not None)
                and offsetSegment.startPoint().roundedEquals(
                    lastOffsetSegment.endPoint())):
            #            (offsetSegment.startPoint() == lastOffsetSegment.endPoint())):
            '''
            No join necessary if inflated segments that align perfectly.
            '''
            if debugMode:
                print('No join necessary (segments aligned)')
            '''
            Heal the join.
            '''
            endpoint = offsetSegment.startPoint().midpoint(
                lastOffsetSegment.endPoint())
            offsetSegment.setStartPoint(endpoint)
            lastOffsetSegment.setEndPoint(endpoint)

            continue

        lastTangent = lastSrcSegment.endTangent()
        nextTangent = srcSegment.startTangent()
        lastAngle = lastTangent.atan2()
        nextAngle = nextTangent.atan2()
        angleDiff = normalizeRadiansDiff(nextAngle - lastAngle)
        if debugMode:
            print('lastTangent', lastTangent)
            print('nextTangent', nextTangent)
            print('lastAngle', lastAngle)
            print('nextAngle', nextAngle)
            print('angleDiff', angleDiff)

        if angleDiff < 0 or angleDiff == math.pi:
            if debugMode:
                print('rounding join')

#                if lastSegmentInvalid and not deflating:
            if lastSegmentInvalid:
                '''
                No join necessary if last segment was invalid.
                '''
                if debugMode:
                    print('No join necessary (last segment invalid)')
            else:
                roundingCenter = srcSegment.startPoint()
                roundingc = TFSOval(roundingCenter, hDistance, vDistance)
                #                    roundingStartAngle = offsetSegment.startPoint().minus(roundingCenter).atan2()
                #                    roundingEndAngle = lastOffsetSegment.endPoint().minus(roundingCenter).atan2()
                roundingStartAngle = srcSegment.startTangent().atan2(
                ) + math.pi * 0.5
                roundingEndAngle = lastSrcSegment.endTangent().atan2(
                ) + math.pi * 0.5

                if debugMode:
                    #                        print 'angleDiff', angleDiff
                    #                        print 'segment', segment.description()
                    #                        print 'lastSegment', lastSegment.description()
                    print('roundingCenter', roundingCenter)
                    print('roundingStartAngle', roundingStartAngle)
                    print('roundingEndAngle', roundingEndAngle)

                try:
                    roundingPath = roundingc.createArc(
                        roundingStartAngle, roundingEndAngle).reverse()
                except TFSValidationException as e:
                    '''
                    If its too short to round, just use a straight segment.
                    '''
                    continue

                # Make sure the rounding endpoints exactly align.
                if lastOffsetSegment is not None:
                    roundingPath[0].setStartPoint(lastOffsetSegment.endPoint())
                if offsetSegment is not None:
                    roundingPath[-1].setEndPoint(offsetSegment.startPoint())
#                    if debugMode:
#                        rounding = openPathWithPoints(rounding.startPoint(), roundingCenter, rounding.endPoint())
                if debugMode:
                    debugPath('roundingPath', roundingPath)

                roundingPaths[index] = roundingPath
                if debugMode:
                    print('inserting join')
                    for value in roundingPath.segments:
                        print('\t', '*', index, value.description())
        else:
            if debugMode:
                print('cross join')
            if ((offsetSegment is not None)
                    and (lastOffsetSegment is not None)):
                joinTangent0 = offsetSegment.startPoint().minus(
                    lastOffsetSegment.endPoint())
                joinTangent1 = offsetSegment.endPoint().minus(
                    lastOffsetSegment.endPoint())
                joinAffinity0 = joinTangent0.dotProduct(nextTangent)
                joinAffinity1 = joinTangent1.dotProduct(nextTangent)
                #                if debugMode:
                #            print 'nextTangent', nextTangent
                #            print 'joinTangent0', joinTangent0
                #            print 'joinTangent1', joinTangent1
                #            print 'joinAffinity0', joinAffinity0
                #            print 'joinAffinity1', joinAffinity1
                if (joinAffinity0 < 0) and (joinAffinity1 < 0):
                    if debugMode:
                        print('invalid segment')
#                        lastSegment = segment
                    lastSegmentInvalid = True
                    continue

        lastSegmentInvalid = False

#    if debugMode:
#        raise TFSContoursException('argh', [TFSPath(False, segment) for segment in newSegments])

    if not (len(offsetSegments) == len(roundingPaths) == len(path)):
        print('offsetSegments, roundingPaths, path', len(offsetSegments),
              len(roundingPaths), len(path))
        raise Exception('Unexpected segments')
    newSegments = []
    for offsetSegment, roundingPath in zip(offsetSegments, roundingPaths):
        if roundingPath is not None:
            newSegments.extend(roundingPath.segments)
        if offsetSegment is not None:
            newSegments.append(offsetSegment)
    newSegments = [
        segment.roundWithDefaultPrecision() for segment in newSegments
    ]
    newPaths = [TFSPath(False, segment) for segment in newSegments]

    if debugMode:
        debugPaths('newPaths', newPaths)

    debugPaths('_flatePathLeft midpoint newPaths', newPaths)
    #    newPath = concatenatePath(True, *newSegments)

    #    raise TFSContoursException('argh', newPaths)

    if TIME_INFLATE_DEFLATE_PATHS:
        time1 = time.time()
        print('\t', 'TFSSilhouette.deflatePaths.core', time1 - time0)

#    unTesselatedPaths = newPaths
#    if debugMode:
#        raise TFSContoursException('argh', unTesselatedPaths)

    if TIME_INFLATE_DEFLATE_PATHS:
        time0 = time.time()
#    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, ignoreStrayEdges=deflating)
#    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, ignoreStrayEdges=deflating, debugMode=debugMode)
    tesselatedPaths, intersections = FTesselation().tesselateContours(
        newPaths,
        reorientPaths=False,
        ignoreStrayEdges=True,
        debugMode=debugMode)
    #    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, ignoreStrayEdges=True, debugMode=debugMode)
    #    newPaths, intersections = FTesselation().tesselateContours(newPaths, reorientPaths=False, debugMode=True)

    debugPaths('_flatePathLeft tesselatedPaths', tesselatedPaths)

    if TIME_INFLATE_DEFLATE_PATHS:
        time1 = time.time()
        print('\t', 'TFSSilhouette.deflatePaths.tesselateContours',
              time1 - time0)


#    raise TFSContoursException('argh', newPaths)

    if TIME_INFLATE_DEFLATE_PATHS:
        time1 = time.time()
        print('\t', 'TFSSilhouette.deflatePaths.cleanup', time1 - time0)

    return tesselatedPaths