Last active
October 13, 2015 23:18
-
-
Save larscwallin/4271123 to your computer and use it in GitHub Desktop.
Bansai! exports SVG from wonderous Inkscape to BonsaiJS template code. Still in Alpha but works as a proof of concept :)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Bansai! | |
* What is Bansai? | |
This is a script extension for Inkscape.It was inspired by the splendid BonsaiJS library. As well as | |
my lazy disposition, which does not like "coding" graphics ;) | |
So in short Bansai lets you select one or elements in Inkscape, and export them to BonsaiJS JSON notation. | |
* What is supported by this version? | |
A little, and still a lot :) At the moment I have had time to implement support for | |
- Path elements | |
- Group elements (also nested) | |
- Transformation matrix | |
- Filters | |
- Gradients | |
These initial features can get you pretty far as most, or all, SVG shapes can be described using | |
one or more of the path types available. | |
* What is NOT supported by this version? | |
A lot of course, such as | |
- SVG shapes such as Rect and Ellipse | |
- Fonts and Text | |
Thanks to Bonsai its really easy for anyone with a basic knowledge of Python, | |
JavaScript and SVG to help with development :) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Update 20121221 17:30 | |
Removed the "feature" that made toggeling of layer visibility neccesary. This however will | |
make all elements, also on hidden layers, present in the exported json. | |
Also fixed a file system bug thanks to Tobias | |
-------------------------------------------------------------------------------------------------- | |
Update 20121221 16:25 | |
Fixed some silly bugs. Now opacity works for fill and stroke again :) | |
-------------------------------------------------------------------------------------------------- | |
Update 20121221 15:48 | |
Started implementing gradient support. Lots of work compared to the previous stuff! | |
Help would be awesome :D | |
-------------------------------------------------------------------------------------------------- | |
Update 20121219 19:06 | |
Added blur filter support to the Bansai js code :) | |
-------------------------------------------------------------------------------------------------- | |
Update 20121219 | |
Gradients added to json output: | |
{ | |
'radialGradient':{ | |
'fx':'281.42856', | |
'fy':'323.79074', | |
'stops':[ | |
{ | |
'stop-color':'#000000', | |
'stop-opacity':'1', | |
'id':'stop840', | |
'offset':'0' | |
}, | |
{ | |
'stop-color':'#000000', | |
'stop-opacity':'0.49803922', | |
'id':'stop848', | |
'offset':'0.35054708' | |
}, | |
{ | |
'stop-color':'#000000', | |
'stop-opacity':'0', | |
'id':'stop842', | |
'offset':'1' | |
} | |
], | |
'gradientUnits':'userSpaceOnUse', | |
'collect':'always', | |
'cy':'323.79074', | |
'cx':'281.42856', | |
'gradientTransform':[ | |
[ | |
0.96764083999999995, | |
0.08781738, | |
-19.327677000000001 | |
], | |
[ | |
-0.076970060000000007, | |
1.1040095999999999, | |
-12.015768 | |
] | |
], | |
'r':'95.714287', | |
'id':'radialGradient850' | |
} | |
} | |
-------------------------------------------------------------------------------------------------- | |
Update 20121218 20:50 | |
Implemented inclusion of filters: | |
{ | |
'filter':{ | |
'feTurbulence':{ | |
'baseFrequency':'0.2', | |
'seed':'0', | |
'result':'result7', | |
'numOctaves':'1', | |
'type':'fractalNoise', | |
'id':'feTurbulence853' | |
}, | |
'feGaussianBlur':{ | |
'result':'result91', | |
'stdDeviation':'8', | |
'id':'feGaussianBlur869', | |
'in':'result0' | |
}, | |
'feOffset':{ | |
'result':'result4', | |
'id':'feOffset835', | |
'dx':'0', | |
'dy':'1.4' | |
}, | |
'feMorphology':{ | |
'result':'result0', | |
'radius':'4', | |
'id':'feMorphology867', | |
'in':'fbSourceGraphic' | |
}, | |
'feConvolveMatrix':{ | |
'divisor':'6.03', | |
'kernelMatrix':'0 -1 0 0 -1 0 0 2 0', | |
'order':'3 3', | |
'result':'result1', | |
'in':'fbSourceGraphic', | |
'id':'feConvolveMatrix823' | |
} | |
} | |
-------------------------------------------------------------------------------------------------- | |
Update 20121216 23:07 | |
Fixed crash in py code when retrieving fill color for paths. | |
-------------------------------------------------------------------------------------------------- | |
Update 20121216 22:42 | |
Separated out the Bansai code into its own "static" closure. This to make it more reusable. | |
-------------------------------------------------------------------------------------------------- | |
Update 20121214 16:37 | |
Added indexing of all added Bonsai objects. This makes it possible to reference them later in | |
your script like this: | |
var tree = stage.options.lookup.labels['tree1']; | |
or | |
var tree = stage.options.lookup.ids['g7816']; | |
And you get a reference back to that instance. | |
This means that you can preserve your Inkscape layer/shape/group naming in you script. Neat.... :) | |
You JS console will also output all the contents of these two indexes. | |
-------------------------------------------------------------------------------------------------- | |
Update 20121214 15:23 | |
Externalized the template HTML code to a separate file. This should be placed, along with the | |
other files, in the Inkscape/share/extensions/ folder. | |
-------------------------------------------------------------------------------------------------- | |
Update 20121213 10:51 | |
Added check to make sure that bounding boxes are never "None" but instead empty arrays. | |
Update 20121213 10:21 | |
I'm such a dork. I forgot to add transform support for Path elements. | |
Fixed now though :) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<inkscape-extension> | |
<_name>Bansai!</_name> | |
<id>com.larscwallin.bansai</id> | |
<dependency type="executable" location="extensions">larscwallin.inx.bansai.py</dependency> | |
<dependency type="executable" location="extensions">inkex.py</dependency> | |
<param name="where" type="string" _gui-text="Where to save the resulting JS file?"></param> | |
<param name="reposition" type="boolean" _gui-text="Reposition each selected element to 0,0?">false</param> | |
<param name="viewresult" type="boolean" _gui-text="Do you wish to view the result?">true</param> | |
<effect> | |
<object-type>all</object-type> | |
<effects-menu> | |
<submenu _name="Export"/> | |
</effects-menu> | |
</effect> | |
<script> | |
<command reldir="extensions" interpreter="python">larscwallin.inx.bansai.py</command> | |
</script> | |
</inkscape-extension> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import inkex | |
import simpletransform | |
import simplepath | |
import os.path | |
from simplestyle import * | |
from time import gmtime, strftime | |
import string | |
import webbrowser | |
import threading | |
from optparse import OptionParser | |
# This line below is only needed if you don't put the script directly into | |
# the installation directory | |
# sys.path.append('/usr/share/inkscape/extensions') | |
""" | |
Bansai! version 0.00001 | |
* What is Bansai? | |
This is a script extension for Inkscape.It was inspired by the splendid BonsaiJS library. As well as | |
my lazy disposition, which does not like "coding" graphics ;) | |
So in short Bansai lets you select one or elements in Inkscape, and export them to BonsaiJS JSON notation. | |
""" | |
class SVGElement(): | |
id='' | |
label='' | |
box=[] | |
path=[] | |
fill='' | |
fill_opacity=1 | |
stroke='' | |
stroke_width=0 | |
stroke_opacity=1 | |
transform='' | |
x=0 | |
y=0 | |
width=0 | |
height=0 | |
nodeRef = None | |
def __init__(self,node = None): | |
self.nodeRef = node | |
class SVGPath(SVGElement): | |
def __init__(self,node = None): | |
SVGElement.__init__(self) | |
class SVGRect(SVGElement): | |
def __init__(self,node = None): | |
SVGElement.__init__(self) | |
class SVGArc(SVGElement): | |
cx=0 | |
cy=0 | |
rx=0 | |
ry=0 | |
def __init__(self,node = None): | |
SVGElement.__init__(self) | |
class SVGGroup(SVGElement): | |
items=[] | |
def __init__(self,node = None): | |
SVGElement.__init__(self) | |
""" | |
This is where the actual fun stuff starts | |
""" | |
# Effect main class | |
class Bansai(inkex.Effect): | |
json_output = [] | |
debug_tab =' ' | |
svg_doc = None | |
svg_doc_width = '' | |
svg_doc_height = '' | |
svg_file = '' | |
bonsaistyle = False | |
reposition = True | |
parsing_context = '' | |
parse_stack = [] | |
def __init__(self): | |
""" | |
Constructor. | |
""" | |
""" | |
First we grab the input parameters that we got from the Inkscape plugin system (see the .inx file) | |
""" | |
inkex.Effect.__init__(self) | |
self.OptionParser.add_option('--where', action = 'store', | |
type = 'string', dest = 'where', default = '', | |
help = 'Where to save the resulting file?') | |
self.OptionParser.add_option('--reposition', action = 'store', | |
type = 'inkbool', dest = 'reposition', default = False, | |
help = 'Reposition elements to 0,0?') | |
self.OptionParser.add_option('--bonsaistyle', action = 'store', | |
type = 'inkbool', dest = 'bonsaistyle', default = False, | |
help = 'Should the output be BonsaiJS specific?') | |
self.OptionParser.add_option('--viewresult', action = 'store', | |
type = 'inkbool', dest = 'viewresult', default = True, | |
help = 'Do you want to view the result?') | |
def effect(self): | |
""" | |
Effect behaviour. | |
Overrides base class method | |
""" | |
self.svg_file = self.args[-1] | |
self.svg_doc = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0] | |
self.svg_doc_width = inkex.unittouu(self.svg_doc.get('width')) | |
self.svg_doc_height = inkex.unittouu(self.svg_doc.get('height')) | |
self.where = self.options.where | |
self.reposition = self.options.reposition | |
self.bonsaistyle = self.options.bonsaistyle | |
self.viewresult = self.options.viewresult | |
filename = '' | |
success = False | |
#inkex.debug("filter:url(#g4567)".split('filter:url(#')[1].split(')')) | |
self.getselected() | |
if(self.selected.__len__() > 0): | |
#inkex.debug(self.debug_tab + 'Elements selected\n'); | |
self.json_output.append({ | |
'defs':{ | |
'filters':[], | |
'fonts':[], | |
'gradients':[] | |
}, | |
'elements':[] | |
}) | |
parent = self.json_output[0]['elements'] | |
selected = [] | |
layers = self.document.xpath('//svg:svg/svg:g/*',namespaces=inkex.NSS) | |
# Iterate through all selected elements | |
for element in self.selected.values(): | |
selected.append(element.get('id')) | |
#inkex.debug(self.debug_tab + 'selected ' + element.get('id')) | |
for element in layers: | |
#inkex.debug(self.debug_tab + 'Looping element ' + element.get('id')) | |
if(element.get('id') in selected): | |
self.parseElement(element,parent) | |
#inkex.debug(self.debug_tab + 'found ' + element.get('id')) | |
self.debug_tab = self.debug_tab[:-4] | |
else: | |
#inkex.debug(self.debug_tab + 'No elements were selected') | |
#layers = self.document.xpath('//svg:svg/svg:g[@style!="display:none"]',namespaces=inkex.NSS) | |
layers = self.document.xpath('//svg:svg/svg:g',namespaces=inkex.NSS) | |
self.json_output.append({ | |
'svg':'document', | |
'id':'', | |
'name':'', | |
'transform':'', | |
'box':[ | |
0, | |
self.svg_doc_width, | |
0, | |
self.svg_doc_height | |
], | |
'defs':{ | |
'filters':[], | |
'fonts':[], | |
'gradients':[] | |
}, | |
'elements':[] | |
}) | |
parent = self.json_output[0]['elements'] | |
# Iterate through all selected elements | |
for element in layers: | |
self.parseElement(element,parent) | |
self.debug_tab = self.debug_tab[:-4] | |
#inkex.debug(self.debug_tab + '\nDone iterating.\n') | |
#inkex.debug(self.debug_tab + ','.join([str(el) for el in self.json_output])) | |
if(self.where!=''): | |
# The easiest way to name rendered elements is by using their id since we can trust that this is always unique. | |
time_stamp = strftime('%a%d%b%Y%H%M', gmtime()) | |
filename = os.path.join(self.where, 'bansai-'+time_stamp+'.html') | |
content = self.templateOutput('larscwallin.inx.bansai.template.html','{/*bonsai_content*/}') | |
success = self.saveToFile(content,filename) | |
if(success and self.viewresult): | |
self.viewOutput(filename) | |
else: | |
inkex.debug('Unable to write to file "' + filename + '"') | |
#inkex.debug(self.debug_tab + ','.join([str(el) for el in self.json_output])) | |
def normalizeSVG(self): | |
pass | |
def parseGroup(self,node,parent): | |
#inkex.debug(self.debug_tab + 'Parsing group' + node.get('id')) | |
self.debug_tab += ' ' | |
self.parsing_context = 'g' | |
id = node.get('id') | |
transform = simpletransform.parseTransform(node.get('transform','')) | |
label = str(node.get(inkex.addNS('label', 'inkscape'),'')) | |
elements = node.xpath('./*',namespaces=inkex.NSS) | |
box = simpletransform.computeBBox(elements) | |
box = list(box) if box != None else [] | |
group = { | |
'id':id, | |
'name':label, | |
'svg':'g', | |
'transform':transform, | |
'box':box, | |
'elements':[] | |
} | |
parent.append(group) | |
self.parse_stack.append(group) | |
#inkex.debug('Loop through all grouped elements') | |
for child in elements: | |
self.parseElement(child,group["elements"]) | |
self.debug_tab = self.debug_tab[:-4] | |
self.parsing_context = '' | |
self.parse_stack.pop() | |
def parseElement(self,node,parent): | |
type = node.get(inkex.addNS('type', 'sodipodi')) | |
if(type == None): | |
#remove namespace data {....} | |
tag_name = node.tag | |
tag_name = tag_name.split('}')[1] | |
else: | |
tag_name = str(type) | |
id = node.get('id') | |
#inkex.debug(self.debug_tab + 'Got "' + tag_name + '" element ' + id); | |
if(tag_name == 'g'): | |
self.parseGroup(node,parent) | |
elif(tag_name == 'path'): | |
self.parsePath(node,parent) | |
elif(tag_name == 'arc'): | |
self.parsePath(node,parent) | |
elif(tag_name == 'rect'): | |
self.parseRect(node,parent) | |
def parseStyleAttribute(self,str): | |
#inkex.debug(self.debug_tab + 'Got style ' + str) | |
rules = str.split(';') | |
parsed_set = {} | |
result = '' | |
for rule in rules: | |
parts = rule.split(':') | |
if(len(parts) > 1): | |
key = self.camelConvert(parts[0]) | |
val = self.camelConvert(parts[1]) | |
if(key== 'filter'): | |
parsed_set['filter'] = self.parseFilter(val) | |
elif(key == 'fill' and val.find('url(#') > -1): | |
parsed_set['fill-gradient'] = self.parseGradient(val) | |
elif(key == 'stroke' and val.find('url(#') > -1): | |
parsed_set['stroke-gradient'] = self.parseGradient(val) | |
else: | |
parsed_set[key] = val | |
result = parsed_set | |
return result | |
def parseFilter(self,filter_str): | |
# Split out the id from the url reference | |
filter_id = self.parseUrlParam(filter_str) | |
result_list = [] | |
tag_name = '' | |
# Got a valid id? | |
if(filter_id!=''): | |
# Ok lets get the filter element which holds all actual instructions | |
filters = self.document.xpath('//svg:svg/svg:defs/svg:filter[@id="'+filter_id+'"]/*',namespaces=inkex.NSS) | |
for node in filters: | |
inkex.debug(self.debug_tab + 'Looping filter ' + node.get('id')) | |
tag_name = self.parseTagName(node.tag) | |
filter = { | |
'svg':tag_name | |
} | |
# Grab all the parameters and values for the current filter | |
for param,val in node.items(): | |
param = self.parseTagName(param) | |
inkex.debug(self.debug_tab + 'param ' + param + ' val ' + val) | |
filter[param] = val | |
result_list.append(filter) | |
return result_list | |
def parsePath(self,node,parent): | |
#self.parsing_context = 'path' | |
style = node.get('style') | |
style = self.parseStyleAttribute(style) | |
transform = simpletransform.parseTransform(node.get('transform','')) | |
path_array = simplepath.parsePath(node.get('d')) | |
path = { | |
'id':node.get('id'), | |
'svg':'path', | |
'label':str(node.get(inkex.addNS('label', 'inkscape'),'')), | |
'box':list(simpletransform.computeBBox([node])), | |
'transform':transform, | |
'path':path_array, | |
'd':node.get('d',''), | |
'attr':{ | |
'fillColor':style.get('fill',''), | |
'fillGradient':style.get('fillGradient',''), | |
'fillOpacity':style.get('fillOpacity','1'), | |
'opacity':style.get('opacity','1'), | |
'strokeColor':style.get('stroke',''), | |
'strokeGradient':style.get('strokeGradient',''), | |
'strokeWidth':style.get('strokeWidth','0'), | |
'strokeOpacity':style.get('strokeOpacity','1'), | |
'filters':style.get('filter','') | |
} | |
} | |
#inkex.debug('Path resides in group ' + self.parse_stack[len(self.parse_stack)-1]['id']) | |
if(self.reposition): | |
path['path'] = self.movePath(path,0,0,'tl') | |
else: | |
path['path'] = simplepath.formatPath(path_array) | |
path['box'] = list(path['box']) if path['box'] != None else [] | |
parent.append(path) | |
""" | |
movePath changes a paths bounding box x,y extents to a new, absolute, position. | |
In other words, this function does not use translate for repositioning. | |
Note: The origin parameter is not currently used but will soon let you choose | |
which origin point (top left, top right, bottom left, bottom right, center) | |
to use. | |
""" | |
def movePath(self,node,x,y,origin): | |
path = node.get('path') | |
box = node.get('box') | |
#inkex.debug(box) | |
offset_x = (box[0] - x) | |
offset_y = (box[2] - (y)) | |
#inkex.debug('Will move path "'+id+'" from x, y ' + str(box[0]) + ', ' + str(box[2])) | |
#inkex.debug('to x, y ' + str(x) + ', ' + str(y)) | |
#inkex.debug('The x offset is ' + str(offset_x)) | |
#inkex.debug('The y offset is = ' + str(offset_y)) | |
for cmd in path: | |
params = cmd[1] | |
i = 0 | |
while(i < len(params)): | |
if(i % 2 == 0): | |
#inkex.debug('x point at ' + str( round( params[i] ))) | |
params[i] = (params[i] - offset_x) | |
#inkex.debug('moved to ' + str( round( params[i] ))) | |
else: | |
#inkex.debug('y point at ' + str( round( params[i]) )) | |
params[i] = (params[i] - offset_y) | |
#inkex.debug('moved to ' + str( round( params[i] ))) | |
i = i + 1 | |
#inkex.debug(simplepath.formatPath(path)) | |
return simplepath.formatPath(path) | |
def parseRect(self,node,parent): | |
#self.parsing_context = 'rect' | |
style = node.get('style') | |
style = self.parseStyleAttribute(style) | |
rect = { | |
'id':node.get('id',''), | |
'svg':'rect', | |
'label':str(node.get(inkex.addNS('label', 'inkscape'),'')), | |
'x': node.get('x',0), | |
'y': node.get('y',0), | |
'width':node.get('width',0), | |
'height':node.get('height',0), | |
'box':[], | |
'fill':style.get('fill',''), | |
'fillOpacity':style.get('fillOpacity',''), | |
'opacity':style.get('opacity',''), | |
'stroke':style.get('stroke',''), | |
'strokeWidth':style.get('strokeWidth',''), | |
'strokeOpacity':style.get('strokeOpacity',''), | |
'transform':node.get('transform','') | |
} | |
if(self.reposition): | |
self.x = 0 | |
self.y = 0 | |
parent.append(rect) | |
def parseArc(self,node,parent): | |
#self.parsing_context = 'arc' | |
style = node.get('style') | |
style = self.parseStyleAttribute(style) | |
arc = { | |
'id':node.get('id',''), | |
'svg':'arc', | |
'label':str(node.get(inkex.addNS('label', 'inkscape'),'')), | |
'cx': node.get(inkex.addNS('cx', 'sodipodi'),''), | |
'cy':node.get(inkex.addNS('cy', 'sodipodi'),''), | |
'rx':node.get(inkex.addNS('rx', 'sodipodi'),''), | |
'ry':node.get(inkex.addNS('ry', 'sodipodi'),''), | |
'path':simplepath.parsePath(node.get('d')), | |
'd':node.get('d',''), | |
'box':list(simpletransform.computeBBox([node])), | |
'fill':style.get('fill',''), | |
'fill-opacity':style.get('fillOpacity',''), | |
'stroke':style.get('stroke',''), | |
'stroke-width':style.get('strokeWidth',''), | |
'stroke-opacity':style.get('strokeOpacity',''), | |
'transform':node.get('transform','') | |
} | |
if(self.reposition): | |
arc['path'] = self.movePath(node,0,0,'tl') | |
else: | |
arc['path'] = arc['d'] | |
parent.append(arc) | |
def parseDef(self,node,parent): | |
pass | |
def parseGradient(self,gradient_str): | |
# Split out the id from the url reference | |
gradient_use_id = self.parseUrlParam(gradient_str) | |
gradient_use = {} | |
gradient_href_id = '' | |
gradient_href = {} | |
result_list = {} | |
tag_name = '' | |
gradient_stops = [] | |
gradient_params = {} | |
# Got a valid id? | |
if(gradient_use_id != ''): | |
# | |
gradient_use = self.document.xpath('//svg:svg/svg:defs/*[@id="'+ gradient_use_id +'"]',namespaces=inkex.NSS)[0] | |
tag_name = self.parseTagName(gradient_use.tag) | |
gradient_params['stops'] = [] | |
#inkex.debug(self.debug_tab + 'Gradient ' + tag_name) | |
# Grab all the parameters and values for the current gradient | |
for param,val in gradient_use.items(): | |
#inkex.debug(self.debug_tab + 'param ' + param + ' val ' + val) | |
param = self.parseTagName(param) | |
if(param == 'href'): | |
# Inkscape uses one-to-many rel for gradients. We need to get the base config | |
# element which has params for color and stops. | |
gradient_href_id = val.split('#')[1] | |
gradient_href = self.document.xpath('//svg:svg/svg:defs/*[@id="'+ gradient_href_id +'"]/*',namespaces=inkex.NSS) | |
#inkex.debug(self.debug_tab + 'href to ' + gradient_href_id) | |
#inkex.debug(self.debug_tab + 'Looping through ' + str(len(gradient_href)) + ' gradient parameter elements') | |
for node in gradient_href: | |
#inkex.debug(self.debug_tab + 'Current parameter element ' + node.tag) | |
gradient_stop = {} | |
for param,val in node.items(): | |
param = self.parseTagName(param) | |
#inkex.debug(self.debug_tab + 'Looping through gradient parameter attributes of ' + param) | |
if(param == 'style'): | |
#inkex.debug(self.debug_tab + 'Parsing style ' + val) | |
gradient_stop.update(self.parseStyleAttribute(val)) | |
else: | |
#inkex.debug(self.debug_tab + 'Adding param/value : ' + param + '/' + val) | |
gradient_stop[param] = val | |
#inkex.debug(self.debug_tab + 'Adding stop ' + gradient_stop['id']) | |
gradient_params['stops'].append(gradient_stop) | |
elif(param == 'gradientTransform'): | |
transform = simpletransform.parseTransform(val) | |
gradient_params[param] = transform | |
else: | |
gradient_params[param] = val | |
gradient_params['svg'] = tag_name | |
result_list = gradient_params | |
return result_list | |
""" | |
stops Array | Object Color stops in the form: `['red','yellow',...]` or `[['red', 0], ['green', 50], ['#FFF', 100]]` i.e. Sub-array [0] is color and [1] is percentage As an object: { 0: 'yellow', 50: 'red', 100: 'green' } | |
direction Number | String Direction in degrees or a string, one of: `top`, `left`, `right`, `bottom`, `top left`, `top right`, `bottom left`, `bottom right` | |
matrix Matrix <optional> | |
Matrix transform for gradient | |
repeat String <optional> | |
How many times to repeat the gradient | |
units String <optional> | |
Either 'userSpace' or 'boundingBox'. | |
stops Array Color stops in the form: `['red','yellow',...]` or `[['red', 0], ['green', 50], ['#FFF', 100]]` i.e. Sub-array [0] is color and [1] is percentage | |
r Number <optional> | |
Radius in percentage (default: 50) | |
cx Number <optional> | |
X coordinate of center of gradient in percentage (default: 50) | |
cy Number <optional> | |
Y coordinate of center of gradient in percentage (default: 50) | |
matrix Matrix <optional> | |
Matrix transform for gradient | |
repeat String <optional> | |
How many times to repeat the gradient | |
units String <optional> | |
Either 'userSpace' or 'boundingBox'. | |
""" | |
def parseFont(self,node,parent): | |
pass | |
def parseGlyph(self,node,parent): | |
pass | |
def pathToObject(self,node): | |
pass | |
def repositionGroupedElements(self, box, elements): | |
pass | |
def parseUrlParam(self,url): | |
return url.split('url(#')[1].split(')')[0] | |
def camelConvert(self,str): | |
camel = str.split('-') | |
if(len(camel) == 2): | |
str = camel[0] + camel[1].title() | |
return str | |
def parseTagName(self,tag): | |
tag_name = tag.split('}') | |
if(len(tag_name) > 1): | |
return tag_name[1] | |
else: | |
return tag | |
def viewOutput(self,url): | |
vwswli = VisitWebSiteWithoutLockingInkscape() | |
vwswli.url = url | |
vwswli.start() | |
def templateOutput(self,templateName = '',placeholder = ''): | |
if(placeholder == ''): | |
inkex.debug('Bonsai.templateOutput: Mandatory argument "placeholder" missing. Aborting.') | |
return False | |
if(templateName == ''): | |
inkex.debug('Bonsai.templateOutput: Mandatory argument "templateName" missing. Aborting.') | |
return False | |
FILE = open(templateName,'r') | |
if(FILE): | |
template = FILE.read() | |
FILE.close() | |
if(len(template) > 0): | |
content = ','.join([str(el) for el in self.json_output]) | |
tplResult = string.replace(template,placeholder,content) | |
return tplResult | |
else: | |
inkex.debug('Bonsai.templateOutput: Empty template file "'+templateName+'". Aborting.') | |
return False | |
else: | |
inkex.debug('Bonsai.templateOutput: Unable to open template file "'+templateName+'". Aborting.') | |
return False | |
def saveToFile(self,content,filename): | |
FILE = open(filename,'w') | |
if(FILE): | |
FILE.write(content) | |
FILE.close() | |
return True | |
else: | |
inkex.debug('Bonsai.templateOutput: Unable to open output file "'+filename+'". Aborting.') | |
return False | |
class VisitWebSiteWithoutLockingInkscape(threading.Thread): | |
url = '' | |
def __init__(self): | |
threading.Thread.__init__ (self) | |
def run(self): | |
webbrowser.open('file://' + self.url) | |
# Create effect instance and apply it. | |
effect = Bansai() | |
effect.affect(output=False) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title></title> | |
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'> | |
</head> | |
<body> | |
<script src='http://cdnjs.cloudflare.com/ajax/libs/bonsai/0.4.1/bonsai.min.js'></script> | |
<div id='stage'></div> | |
<script> | |
stage = document.getElementById('stage'); | |
bonsai.run(stage, { | |
code: function() { | |
bansai = { | |
ids: {}, | |
labels:{}, | |
groups:{}, | |
stage:null, | |
init:function(stage,svgDoc){ | |
backgroundLayer = bansai.createBackgroundLayer(); | |
bansai.stage = stage; | |
svgDoc.elements.forEach(function(el) { | |
if(el.svg === 'g'){ | |
bansai.addGroup(el,backgroundLayer); | |
}else{ | |
bansai.addPath(el,backgroundLayer); | |
} | |
}); | |
console.log("Dump of indexed SVG label attributes:" + bansai.objToString(bansai.labels)); | |
console.log("Dump of indexed SVG id attributes:" + bansai.objToString(bansai.ids)); | |
}, | |
createBackgroundLayer:function(){ | |
return new Group().attr({x:0, y:0}).addTo(stage); | |
}, | |
addGroup:function(node,parent){ | |
var group = new Group(); | |
node.elements.forEach(function(el){ | |
if(el.svg === 'g'){ | |
bansai.addGroup(el,group); | |
}else if(el.svg === 'path'){ | |
bansai.addPath(el,group); | |
} | |
}); | |
if(node.transform!==''){ | |
m = new Matrix(); | |
m.scale(node.transform[0][0],node.transform[1][1]); | |
m.rotate(node.transform[1][0]); | |
m.translate(node.transform[0][2],node.transform[1][2]); | |
group.attr('matrix',m); | |
} | |
bansai.ids[node.id] = group; | |
if (node.label !== '') { | |
if(!bansai.labels[node.label]){ | |
bansai.labels[node.label] = []; | |
} | |
bansai.labels[node.label].push(group); | |
} | |
group.addTo(parent); | |
}, | |
addPath:function(node,parent){ | |
path = new Path(node.path).attr({ | |
fillColor: node.attr.fillColor, | |
fillOpacity: node.attr.fillOpacity, | |
strokeColor: node.attr.strokeColor, | |
strokeWidth: node.attr.strokeWidth | |
}); | |
if(node.transform!==''){ | |
m = new Matrix(); | |
m.scale(node.transform[0][0],node.transform[1][1]); | |
m.rotate(node.transform[1][0]); | |
m.translate(node.transform[0][2],node.transform[1][2]); | |
path.attr('matrix',m); | |
} | |
if(node.attr.filters!==''){ | |
node.attr.filters.forEach(function(el) { | |
switch(el.svg){ | |
case 'feGaussianBlur': | |
f = new filter.Blur(el.stdDeviation); | |
path.attr('filters',f); | |
break; | |
} | |
}); | |
} | |
if(node.attr.fillGradient!==''){ | |
//console.log('Got a fill gradient'); | |
gradientType = node.attr.fillGradient.svg; | |
switch(gradientType){ | |
case 'linearGradient': | |
//console.log('Type, linear gradient'); | |
gradient = node.attr.fillGradient; | |
stops = []; | |
// Set up stops array | |
gradient.stops.forEach(function(el){ | |
color = bansai.hexToRgba(el.stopColor,el.stopOpacity); | |
color = ('rgba('+color.r+','+color.g+','+color.b+','+color.a+')'); | |
//console.log('stop color / offset ' + color + ' / ' + el.offset); | |
stops.push(color,el.offset); | |
}); | |
bonsaiGradient = bonsai.gradient.linear('top',stops); | |
//console.log(bonsaiGradient); | |
path.attr({ | |
fillGradient:bonsaiGradient | |
}); | |
/* | |
fillGradient = bonsai.gradient.linear('left', [ | |
['rgb(255,25,5)',20], | |
['green', 60], | |
['yellow',20] | |
]); | |
bonsai.Path.rect(400, 0, 100, 250).attr({ | |
'fillGradient' : fillGradient | |
}).addTo(stage); | |
'fillGradient':{ | |
'gradientUnits':'userSpaceOnUse', | |
'y2':'115.21932', | |
'svg':'linearGradient', | |
'stops':[ | |
{ | |
'stop-color':'#000000', | |
'stop-opacity':'1', | |
'id':'stop14', | |
'offset':'0' | |
}, | |
{ | |
'stop-color':'#000000', | |
'stop-opacity':'0', | |
'id':'stop16', | |
'offset':'1' | |
} | |
], | |
'x2':'400', | |
'collect':'always', | |
'y1':'489.50504', | |
'x1':'397.14285', | |
'id':'linearGradient18' | |
} | |
*/ | |
break; | |
case 'radialGradient': | |
//console.log('Type, radial gradient'); | |
/* | |
'fillGradient':{ | |
'fx':'355.71429', | |
'fy':'308.07648', | |
'stops':[ | |
{ | |
'stop-color':'#000000', | |
'stop-opacity':'1', | |
'id':'stop14', | |
'offset':'0' | |
}, | |
{ | |
'stop-color':'#000000', | |
'stop-opacity':'0', | |
'id':'stop16', | |
'offset':'1' | |
} | |
], | |
'gradientUnits':'userSpaceOnUse', | |
'collect':'always', | |
'cy':'308.07648', | |
'cx':'355.71429', | |
'gradientTransform':[ | |
[ | |
1.0, | |
0.0, | |
0.0 | |
], | |
[ | |
0.0, | |
1.0370439, | |
-11.41234 | |
] | |
], | |
'svg':'radialGradient', | |
'r':'154.25716', | |
'id':'radialGradient827' | |
} | |
*/ | |
break; | |
default: | |
//console.log('Unrecognized gradient'); | |
} | |
} | |
if(node.attr.strokeGradient!==''){ | |
//console.log('Got a stroke gradient'); | |
gradientType = node.attr.strokeGradient.svg; | |
switch(gradientType){ | |
case 'linearGradient': | |
//console.log('Type, linear gradient'); | |
break; | |
case 'radialGradient': | |
console.log('Type, radial gradient'); | |
break; | |
default: | |
console.log('Unrecognized gradient'); | |
} | |
} | |
bansai.ids[node.id] = path; | |
if (node.label !== '') { | |
if(!bansai.labels[node.label]){ | |
bansai.labels[node.label] = []; | |
} | |
bansai.labels[node.label].push(path); | |
} | |
path.addTo(parent); | |
}, | |
addFlowRoot:function (node,parent){ | |
}, | |
addFlowPara:function (node,parent){ | |
}, | |
addFlowRegion:function(node,parent){ | |
}, | |
getRotationAngle:function(coords){ | |
if(coords.length < 4) return false; | |
var deg2rad = Math.PI/180; | |
var rad2deg = 180/Math.PI; | |
var dx = coords[0] - coords[1]; | |
var dy = coords[2] - coords[3]; | |
var rads = Math.atan2(dx,dy); | |
var degs = rads * rad2deg; | |
return degs; | |
}, | |
setRotationAngle:function(coords, direction){ | |
var resultCoords = []; | |
if(direction === "left"){ | |
resultCoords = [100,0,0,0]; | |
} else if(direction === "right"){ | |
resultCoords = [0,100,0,0]; | |
} else if(direction === "down"){ | |
resultCoords = [0,0,0,100]; | |
} else if(direction === "up"){ | |
resultCoords = [0,0,100,0]; | |
} else if(typeof direction === "number"){ | |
var pointOfAngle = function(a) { | |
return { | |
x:Math.cos(a), | |
y:Math.sin(a) | |
}; | |
}; | |
var degreesToRadians = function(d) { | |
return (d * (180 / Math.PI)); | |
}; | |
var eps = Math.pow(2, -52); | |
var angle = (direction % 360); | |
var startPoint = pointOfAngle(degreesToRadians(180 - angle)); | |
var endPoint = pointOfAngle(degreesToRadians(360 - angle)); | |
if(startPoint.x <= 0 || Math.abs(startPoint.x) <= eps) | |
startPoint.x = 0; | |
if(startPoint.y <= 0 || Math.abs(startPoint.y) <= eps) | |
startPoint.y = 0; | |
if(endPoint.x <= 0 || Math.abs(endPoint.x) <= eps) | |
endPoint.x = 0; | |
if(endPoint.y <= 0 || Math.abs(endPoint.y) <= eps) | |
endPoint.y = 0; | |
resultCoords = [startPoint.x,endPoint.x,startPoint.y,endPoint.y]; | |
} | |
return resultCoords; | |
}, | |
objToString:function(obj) { | |
var str = ''; | |
for (var p in obj) { | |
if (obj.hasOwnProperty(p)) { | |
str += p + '::' + obj[p] + '\n'; | |
} | |
} | |
return "\n" + str; | |
}, | |
hexToRgba:function(hex,alpha) { | |
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") | |
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; | |
hex = hex.replace(shorthandRegex, function(m, r, g, b) { | |
return r + r + g + g + b + b; | |
}); | |
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); | |
return result ? { | |
r: parseInt(result[1], 16), | |
g: parseInt(result[2], 16), | |
b: parseInt(result[3], 16), | |
a: alpha | |
} : null; | |
} | |
}; | |
var svgDoc = {/*bonsai_content*/}; | |
bansai.init(stage,svgDoc); | |
/* | |
Reference your shapes using their Inkscape label, | |
var cloud = bansai.labels['cloud']; | |
cloud.animate('10s',{x:250}); | |
or by their id, | |
var sun = bansai.ids['g9675']; | |
*/ | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment