229 lines
7.0 KiB
Python
229 lines
7.0 KiB
Python
#!/usr/bin/env python
|
|
|
|
# These two lines are only needed if you don't put the script directly into
|
|
# the installation directory
|
|
import sys
|
|
sys.path.append('/usr/share/inkscape/extensions')
|
|
|
|
import math
|
|
|
|
import inkex
|
|
|
|
from simplestyle import *
|
|
|
|
import random
|
|
|
|
class blobsEffect(inkex.Effect):
|
|
"""
|
|
Creates a random blob from a convex hull over n points.
|
|
The expected degree of the polygon is sqrt(n). The corners
|
|
are blunted by the blunt parameter. 0 means sharp. 1 will
|
|
result in loopy splines.
|
|
"""
|
|
def __init__(self):
|
|
"""
|
|
Constructor.
|
|
Defines the parms option of a script.
|
|
"""
|
|
# Call the base class constructor.
|
|
inkex.Effect.__init__(self)
|
|
|
|
# Define string option "--what" with "-w" shortcut and default "World".
|
|
|
|
self.OptionParser.add_option('-n', '--num', action = 'store',
|
|
type = 'int', dest = 'num', default = 25,
|
|
help = 'Number of random points to start with')
|
|
|
|
self.OptionParser.add_option('-b', '--blunt', action = 'store',
|
|
type = 'float', dest = 'blunt', default = 0.3,
|
|
help = 'Bluntness of corners. Should be < 1')
|
|
|
|
self.OptionParser.add_option('-c', '--cave', action = 'store',
|
|
type = 'float', dest = 'cave', default = 0.0,
|
|
help = 'Concavity. Less blobby and more splatty')
|
|
|
|
self.OptionParser.add_option('-x', '--rx', action = 'store',
|
|
type = 'int', dest = 'rx', default = 1000,
|
|
help = 'Size of work area x')
|
|
|
|
self.OptionParser.add_option('-y', '--ry', action = 'store',
|
|
type = 'int', dest = 'ry', default = 1000,
|
|
help = 'Size of work area y')
|
|
|
|
self.OptionParser.add_option('-z', '--sz', action = 'store',
|
|
type = 'float', dest = 'sz', default = 50.,
|
|
help = 'Size of a blob')
|
|
|
|
self.OptionParser.add_option('-g', '--nb', action = 'store',
|
|
type = 'int', dest = 'nb', default = 10,
|
|
help = 'Total number of blobs')
|
|
|
|
self.OptionParser.add_option("", "--Nmain", action="store",
|
|
type="string", dest="active_tab", default='top',
|
|
help="Active tab.")
|
|
|
|
|
|
|
|
def effect(self):
|
|
"""
|
|
Effect behaviour.
|
|
Overrides base class' method.
|
|
"""
|
|
|
|
global cave
|
|
|
|
blunt = float( self.options.blunt )
|
|
cave = float( self.options.cave )
|
|
sz = float( self.options.sz )
|
|
nb = int( self.options.nb )
|
|
rx = int( self.options.rx )
|
|
ry = int( self.options.ry )
|
|
num = int( self.options.num )
|
|
|
|
# Get access to main SVG document element and get its dimensions.
|
|
svg = self.document.getroot()
|
|
|
|
# Create a new layer.
|
|
layer = inkex.etree.SubElement(svg, 'g')
|
|
layer.set(inkex.addNS('label', 'inkscape'), 'Blob Layer' )
|
|
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
|
|
|
style = {
|
|
'fill' : '#000000'
|
|
}
|
|
# inkex.errormsg("black")
|
|
|
|
ctrs = [(random.randrange(rx) , random.randrange(ry))
|
|
for i in range(nb) ]
|
|
for ctr in ctrs :
|
|
points = [(random.gauss(ctr[0], sz) , random.gauss(ctr[1], sz))
|
|
for i in range(num) ]
|
|
|
|
px = hull(points)
|
|
pts = [points[px[i]] for i in range(len(px))]
|
|
|
|
|
|
# Create path element
|
|
path = inkex.etree.Element(inkex.addNS('path','svg'))
|
|
|
|
|
|
|
|
path.set('style', formatStyle(style))
|
|
|
|
pathstring = 'M ' + str(pts[0][0]) + ' ' + str(pts[0][1]) + ' '
|
|
|
|
for j in range(len(pts)):
|
|
k = (j+1) % len(pts)
|
|
kk = (j+2) % len(pts)
|
|
if j==0 :
|
|
(lasth, h1) = sHandles(pts[-1], pts[0], pts[1], blunt)
|
|
(h2, hnext) = sHandles(pts[j], pts[k], pts[kk], blunt)
|
|
pathstring += "C %f %f %f %f %f %f " % (h1[0], h1[1],
|
|
h2[0], h2[1],
|
|
pts[k][0], pts[k][1])
|
|
h1 = hnext
|
|
|
|
pathstring += 'Z'
|
|
path.set('d', pathstring )
|
|
layer.append(path)
|
|
|
|
def sHandles(pre, pt, post, blunt):
|
|
'''I'm proud of this cute little construction for the
|
|
spline handles. No doubt someone has thought of it before
|
|
but, if not, its name is ACHC Andrew's Cute Handle
|
|
Construction. Note: no trig function calls.'''
|
|
try :
|
|
slope = (post[1] - pt[1]) / (post[0] - pt[0])
|
|
except ZeroDivisionError :
|
|
slope = math.copysign(1E30 , post[1] - pt[1])
|
|
lenpre = distance(pre, pt)
|
|
lenpost = distance(pt, post)
|
|
lenr = lenpre**2 / lenpost
|
|
locx = math.copysign( lenr / math.sqrt(1. + slope**2) , post[0] - pt[0])
|
|
mark = ( pre[0] - locx , pre[1] - locx*slope )
|
|
try :
|
|
markslope = (pt[1] - mark[1]) / (pt[0] - mark[0])
|
|
except ZeroDivisionError :
|
|
markslope = math.copysign(1E30 , pt[1] - mark[1])
|
|
prex = math.copysign( lenpre / math.sqrt(1. + markslope**2) ,
|
|
pt[0] - mark[0])
|
|
hpre = (pt[0] - prex*blunt , pt[1] - prex*markslope*blunt)
|
|
postx = prex*lenpost/lenpre
|
|
hpost = (pt[0] + postx*blunt , pt[1] + postx*markslope*blunt)
|
|
return (hpre, hpost)
|
|
|
|
"""Blunt=0.3 makes pleasingly round, mostly convex blobs. 0.4 makes them more
|
|
concave. 0.6 - 1.0 they're getting more and more pointy. 2.0 - 10. and they
|
|
grow appendages like hot-air balloons. 0.1 makes the corners pretty sharp.
|
|
0.0 and it's down to the convex hulls that are the basis of the blobs, that
|
|
is, polygons"""
|
|
|
|
|
|
def distance(a, b) :
|
|
return math.sqrt( (a[0] - b[0])**2 + (a[1] - b[1])**2 )
|
|
|
|
|
|
def hull(arg):
|
|
"""Convex hull by Graham scan."""
|
|
xarr, yarr = zip( * arg )
|
|
ymin = min(yarr)
|
|
ind = findall( yarr, lambda y: y == ymin )
|
|
if len(ind) > 1 :
|
|
xshort = [xarr[j] for j in ind]
|
|
xmin = min(xshort)
|
|
j = ind[xshort.index(xmin)]
|
|
ind = j
|
|
else :
|
|
ind = ind[0]
|
|
all = range(len(xarr))
|
|
del all[ind]
|
|
all.sort(key=lambda i : (xarr[i] - xarr[ind]) /
|
|
math.sqrt( (xarr[i] - xarr[ind])**2 + (yarr[i] - yarr[ind])**2 ),
|
|
reverse=True)
|
|
if len(all) < 3 :
|
|
all.insert(0, ind)
|
|
return all
|
|
ans = [ind]
|
|
for i in all :
|
|
if len(ans) == 1 :
|
|
ans.append(i)
|
|
else :
|
|
while rightTurn(ans[-2], ans[-1], i, arg) :
|
|
ans.pop()
|
|
ans.append(i)
|
|
|
|
return ans
|
|
|
|
def rightTurn(j, k, l, arg) :
|
|
'''Cross product: Ax*By - Ay*Bx = Cz '''
|
|
ax = (arg[k][0] - arg[j][0])
|
|
by = (arg[l][1] - arg[k][1])
|
|
ay = (arg[k][1] - arg[j][1])
|
|
bx = (arg[l][0] - arg[k][0])
|
|
p = ax*by - ay*bx
|
|
dot = ax*bx + ay*by
|
|
cos = dot / math.sqrt((ax**2 + ay**2) * (bx**2 + by**2))
|
|
crt = 1 - cave*2
|
|
|
|
if p <= 0 :
|
|
return cos < crt #We forgive right turns based on /cave/
|
|
else :
|
|
return False
|
|
|
|
|
|
|
|
|
|
def findall(a, f):
|
|
r = []
|
|
for x, j in zip(a, range(len(a))) :
|
|
if f(x) :
|
|
r.append(j)
|
|
return r
|
|
|
|
|
|
# Create effect instance and apply it.
|
|
effect = blobsEffect()
|
|
effect.affect()
|
|
sys.exit( 0 )
|
|
|