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
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
# 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