This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-0.92-deprecated/fablabchemnitz_blobs.py

229 lines
7.0 KiB
Python
Raw Normal View History

2019-11-14 20:05:10 +01:00
#!/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 )