#!/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()