159 lines
4.6 KiB
Python
159 lines
4.6 KiB
Python
|
#!/usr/bin/env python
|
||
|
"""
|
||
|
X-agram
|
||
|
Create n-pointed star polygons (pentagram, hexagram, etc)
|
||
|
"""
|
||
|
import inkex
|
||
|
from math import *
|
||
|
|
||
|
def addPathCommand(a, cmd):
|
||
|
for x in cmd:
|
||
|
a.append(str(x))
|
||
|
|
||
|
class XGramEffect(inkex.Effect):
|
||
|
|
||
|
def __init__(self):
|
||
|
inkex.Effect.__init__(self)
|
||
|
self.OptionParser.add_option('--tab',
|
||
|
action = 'store', type = 'string', dest = 'tab')
|
||
|
self.OptionParser.add_option('--points',
|
||
|
action='store', type='int', dest='points', default=5,
|
||
|
help='Number of points (or sides)')
|
||
|
self.OptionParser.add_option('--skip',
|
||
|
action='store', type='int', dest='skip', default=2,
|
||
|
help='Vertex increment when connecting points')
|
||
|
self.OptionParser.add_option('--rotate',
|
||
|
action='store', type='float', dest='rotate', default=0,
|
||
|
help='Rotation angle (clockwise, in degrees)')
|
||
|
self.OptionParser.add_option('--inner-circle',
|
||
|
action='store', type='inkbool', dest='inner_circle', default=False,
|
||
|
help='Connect points via inner circle')
|
||
|
self.OptionParser.add_option('--show-inner-circle',
|
||
|
action='store', type='inkbool', dest='show_inner_circle', default=True,
|
||
|
help='Show inner circle')
|
||
|
self.OptionParser.add_option('--inner-ratio',
|
||
|
action='store', type='int', dest='inner_ratio', default=50,
|
||
|
help='Inner radius percentage (inner radius as a percentage of the outer radius)')
|
||
|
|
||
|
def effect(self):
|
||
|
layer = self.current_layer;
|
||
|
|
||
|
if len(self.selected) == 0:
|
||
|
inkex.errormsg('Please select a circle or ellipse.')
|
||
|
exit()
|
||
|
|
||
|
numValid = 0
|
||
|
for id, obj in self.selected.iteritems():
|
||
|
cx,cy, rx,ry = 0,0, 0,0
|
||
|
style = ''
|
||
|
isValid = False
|
||
|
if obj.tag == inkex.addNS('circle','svg'):
|
||
|
isValid = True
|
||
|
cx = float(obj.get('cx'))
|
||
|
cy = float(obj.get('cy'))
|
||
|
rx = float(obj.get('r'))
|
||
|
ry = rx
|
||
|
elif obj.tag == inkex.addNS('ellipse', 'svg'):
|
||
|
isValid = True
|
||
|
cx = float(obj.get('cx'))
|
||
|
cy = float(obj.get('cy'))
|
||
|
rx = float(obj.get('rx'))
|
||
|
ry = float(obj.get('ry'))
|
||
|
elif obj.tag == inkex.addNS('path', 'svg'):
|
||
|
if obj.get(inkex.addNS('type', 'sodipodi')) == 'arc':
|
||
|
isValid = True
|
||
|
cx = float(obj.get(inkex.addNS('cx', 'sodipodi')))
|
||
|
cy = float(obj.get(inkex.addNS('cy', 'sodipodi')))
|
||
|
rx = float(obj.get(inkex.addNS('rx', 'sodipodi')))
|
||
|
ry = float(obj.get(inkex.addNS('ry', 'sodipodi')))
|
||
|
|
||
|
if not isValid:
|
||
|
continue;
|
||
|
|
||
|
numValid += 1
|
||
|
style = obj.get('style')
|
||
|
transform = obj.get('transform')
|
||
|
isEllipse = False
|
||
|
if rx != ry:
|
||
|
isEllipse = True
|
||
|
|
||
|
sides = self.options.points
|
||
|
skip = self.options.skip
|
||
|
rotate = self.options.rotate
|
||
|
useInnerCircle = self.options.inner_circle
|
||
|
showInnerCircle = self.options.show_inner_circle
|
||
|
innerRatio = float(self.options.inner_ratio) / 100.0
|
||
|
|
||
|
if useInnerCircle and showInnerCircle:
|
||
|
if not isEllipse:
|
||
|
cin = inkex.etree.SubElement(layer, inkex.addNS('circle','svg'))
|
||
|
cin.set('r', str(rx * innerRatio))
|
||
|
else:
|
||
|
cin = inkex.etree.SubElement(layer, inkex.addNS('ellipse','svg'))
|
||
|
cin.set('rx', str(rx * innerRatio))
|
||
|
cin.set('ry', str(ry * innerRatio))
|
||
|
cin.set('cx', str(cx))
|
||
|
cin.set('cy', str(cy))
|
||
|
cin.set('style', style)
|
||
|
if transform:
|
||
|
cin.set('transform', transform)
|
||
|
|
||
|
tau = 2*pi
|
||
|
origin = -(tau / 4) + (rotate * pi / 180)
|
||
|
out_pts = []
|
||
|
in_pts = []
|
||
|
for i in range(sides):
|
||
|
# Outer points (on outer circle)
|
||
|
theta = (i * (tau / sides))
|
||
|
px = cx + rx * cos(origin + theta)
|
||
|
py = cy + ry * sin(origin + theta)
|
||
|
out_pts.append([px, py])
|
||
|
|
||
|
# Inner points (on inner circle)
|
||
|
theta = ((i + (skip / 2.0)) * (tau / sides))
|
||
|
px = cx + rx * innerRatio * cos(origin + theta)
|
||
|
py = cy + ry * innerRatio * sin(origin + theta)
|
||
|
in_pts.append([px, py])
|
||
|
|
||
|
pts = []
|
||
|
pt_done = {}
|
||
|
for i in range(sides):
|
||
|
if i in pt_done:
|
||
|
continue;
|
||
|
|
||
|
p1 = out_pts[i]
|
||
|
addPathCommand(pts, ['M', p1[0], p1[1]])
|
||
|
|
||
|
pt_done[i] = True
|
||
|
start_index = i
|
||
|
curr = start_index
|
||
|
next = (curr + skip) % sides
|
||
|
while next != start_index:
|
||
|
p = out_pts[next]
|
||
|
pt_done[next] = True
|
||
|
|
||
|
if useInnerCircle:
|
||
|
addPathCommand(pts, ['L', in_pts[curr][0], in_pts[curr][1]])
|
||
|
|
||
|
addPathCommand(pts, ['L', p[0], p[1]])
|
||
|
|
||
|
curr = next
|
||
|
next = (curr + skip) % sides
|
||
|
if useInnerCircle:
|
||
|
addPathCommand(pts, ['L', in_pts[curr][0], in_pts[curr][1]])
|
||
|
addPathCommand(pts, ['z'])
|
||
|
|
||
|
# Create star polygon as a single path.
|
||
|
l1 = inkex.etree.SubElement(layer, inkex.addNS('path','svg'))
|
||
|
l1.set('style', style)
|
||
|
if transform:
|
||
|
l1.set('transform', transform)
|
||
|
l1.set('d', ' '.join(pts))
|
||
|
|
||
|
if numValid == 0:
|
||
|
inkex.errormsg('Selection must contain a circle or ellipse.')
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
effect = XGramEffect()
|
||
|
effect.affect()
|