mightyscape-1.2/extensions/fablabchemnitz/cookie_cutter/svgpath/shader.py

136 lines
5.4 KiB
Python
Executable File

import math
from operator import itemgetter
class Shader(object):
MODE_EVEN_ODD = 0
MODE_NONZERO = 1
def __init__(self, unshadedThreshold=1., lightestSpacing=3., darkestSpacing=0.5, angle=45, crossHatch=False):
self.unshadedThreshold = unshadedThreshold
self.lightestSpacing = lightestSpacing
self.darkestSpacing = darkestSpacing
self.angle = angle
self.secondaryAngle = angle + 90
self.crossHatch = False
def isActive(self):
return self.unshadedThreshold > 0.000001
def setDrawingDirectionAngle(self, drawingDirectionAngle):
self.drawingDirectionAngle = drawingDirectionAngle
if drawingDirectionAngle is None:
return
if 90 < (self.angle - drawingDirectionAngle) % 360 < 270:
self.angle = (self.angle + 180) % 360
if 90 < (self.secondaryAngle - drawingDirectionAngle) % 360 < 270:
self.secondaryAngle = (self.secondaryAngle + 180) % 360
def shade(self, polygon, grayscale, avoidOutline=True, mode=None):
if mode is None:
mode = Shader.MODE_EVEN_ODD
if grayscale >= self.unshadedThreshold:
return []
intensity = (self.unshadedThreshold-grayscale) / float(self.unshadedThreshold)
spacing = self.lightestSpacing * (1-intensity) + self.darkestSpacing * intensity
lines = Shader.shadePolygon(polygon, self.angle, spacing, avoidOutline=avoidOutline, mode=mode, alternate=(self.drawingDirectionAngle is None))
if self.crossHatch:
lines += Shader.shadePolygon(polygon, self.angle+90, spacing, avoidOutline=avoidOutline, mode=mode, alternate=(self.drawingDirectionAngle is None))
return lines
@staticmethod
def shadePolygon(polygon, angleDegrees, spacing, avoidOutline=True, mode=None, alternate=True):
if mode is None:
mode = Shader.MODE_EVEN_ODD
rotate = complex(math.cos(angleDegrees * math.pi / 180.), math.sin(angleDegrees * math.pi / 180.))
polygon = [(line[0] / rotate,line[1] / rotate) for line in polygon]
spacing = float(spacing)
toAvoid = list(set(line[0].imag for line in polygon)|set(line[1].imag for line in polygon))
if len(toAvoid) <= 1:
deltaY = (toAvoid[0]-spacing/2.) % spacing
else:
# find largest interval
toAvoid.sort()
largestIndex = 0
largestLen = 0
for i in range(len(toAvoid)):
l = ( toAvoid[i] - toAvoid[i-1] ) % spacing
if l > largestLen:
largestIndex = i
largestLen = l
deltaY = (toAvoid[largestIndex-1] + largestLen / 2.) % spacing
minY = min(min(line[0].imag,line[1].imag) for line in polygon)
maxY = max(max(line[0].imag,line[1].imag) for line in polygon)
y = minY + ( - minY ) % spacing + deltaY
if y > minY + spacing:
y -= spacing
y += 0.01
odd = False
all = []
while y < maxY:
intersections = []
for line in polygon:
z = line[0]
z1 = line[1]
if z1.imag == y or z.imag == y: # roundoff generated corner case -- ignore -- TODO
break
if z1.imag < y < z.imag or z.imag < y < z1.imag:
if z1.real == z.real:
intersections.append(( complex(z.real, y), z.imag<y, line))
else:
m = (z1.imag-z.imag)/(z1.real-z.real)
# m * (x - z.real) = y - z.imag
# so: x = (y - z.imag) / m + z.real
intersections.append( (complex((y-z.imag)/m + z.real, y), z.imag<y, line) )
intersections.sort(key=lambda datum: datum[0].real)
thisLine = []
if mode == Shader.MODE_EVEN_ODD:
for i in range(0,len(intersections)-1,2):
thisLine.append((intersections[i], intersections[i+1]))
elif mode == Shader.MODE_NONZERO:
count = 0
for i in range(0,len(intersections)-1):
if intersections[i][1]:
count += 1
else:
count -= 1
if count != 0:
thisLine.append((intersections[i], intersections[i+1]))
else:
raise ValueError()
if odd and alternate:
thisLine = list(reversed([(l[1],l[0]) for l in thisLine]))
if not avoidOutline and len(thisLine) and len(all) and all[-1][1][2] == thisLine[0][0][2]:
# follow along outline to avoid an extra pen bob
all.append( (all[-1][1], thisLine[0][0]) )
all += thisLine
odd = not odd
y += spacing
return [(line[0][0]*rotate, line[1][0]*rotate) for line in all]
if __name__ == '__main__':
polygon=(0+0j, 10+10j, 10+0j, 0+0j)
print(shadePolygon(polygon,90,1))