Skip to content

Instantly share code, notes, and snippets.

@larscwallin
Last active October 13, 2015 23:18
Show Gist options
  • Select an option

  • Save larscwallin/4271123 to your computer and use it in GitHub Desktop.

Select an option

Save larscwallin/4271123 to your computer and use it in GitHub Desktop.

Revisions

  1. larscwallin revised this gist Dec 21, 2012. 2 changed files with 9 additions and 3 deletions.
    3 changes: 0 additions & 3 deletions Bansai!
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,5 @@
    Bansai!

    IMPORTANT - ALL LAYERS MUST BE HIDDEN AND THEN DISPLAYED IN THE LAYERS LIST TO
    GET THE NEEDED STYLE ATTRIBUTE WHICH IS USED BY 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 ;)
    9 changes: 9 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,12 @@
    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 :)
  2. larscwallin revised this gist Dec 21, 2012. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -172,7 +172,8 @@ def effect(self):
    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[@style!="display:none"]',namespaces=inkex.NSS)
    layers = self.document.xpath('//svg:svg/svg:g',namespaces=inkex.NSS)

    self.json_output.append({
    'svg':'document',
    @@ -683,7 +684,7 @@ def __init__(self):
    threading.Thread.__init__ (self)

    def run(self):
    webbrowser.open(self.url)
    webbrowser.open('file://' + self.url)



  3. larscwallin revised this gist Dec 21, 2012. 2 changed files with 37 additions and 31 deletions.
    6 changes: 6 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,9 @@
    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!
    62 changes: 31 additions & 31 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -217,7 +217,7 @@ def effect(self):
    inkex.debug('Unable to write to file "' + filename + '"')

    #inkex.debug(self.debug_tab + ','.join([str(el) for el in self.json_output]))
    def normalizeSVG():
    def normalizeSVG(self):
    pass

    def parseGroup(self,node,parent):
    @@ -295,14 +295,17 @@ def parseStyleAttribute(self,str):

    if(len(parts) > 1):

    if(parts[0] == 'filter'):
    parsed_set['filter'] = self.parseFilter(parts[1])
    elif(parts[0] == 'fill' and parts[1].find('url(#') > -1):
    parsed_set['fill-gradient'] = self.parseGradient(parts[1])
    elif(parts[0] == 'stroke' and parts[1].find('url(#') > -1):
    parsed_set['stroke-gradient'] = self.parseGradient(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[parts[0]] = parts[1]
    parsed_set[key] = val

    result = parsed_set
    return result
    @@ -347,17 +350,6 @@ def parsePath(self,node,parent):
    transform = simpletransform.parseTransform(node.get('transform',''))
    path_array = simplepath.parsePath(node.get('d'))

    #inkex.debug(style)

    ## if(self.bonsaistyle):
    ## inkex.debug('Bonsai style!')
    ## for attr in style:
    ## inkex.debug('Attr ' + attr)
    ## camelAttr = attr.split('-')
    ## if(len(camelAttr) == 2):
    ## attr = camelAttr[0] + camelAttr[1].title()
    ## inkex.debug('Camel version ' + attr)

    path = {
    'id':node.get('id'),
    'svg':'path',
    @@ -368,13 +360,13 @@ def parsePath(self,node,parent):
    'd':node.get('d',''),
    'attr':{
    'fillColor':style.get('fill',''),
    'fillGradient':style.get('fill-gradient',''),
    'fillOpacity':style.get('fill-opacity','1'),
    'fillGradient':style.get('fillGradient',''),
    'fillOpacity':style.get('fillOpacity','1'),
    'opacity':style.get('opacity','1'),
    'strokeColor':style.get('stroke',''),
    'strokeGradient':style.get('stroke-gradient',''),
    'strokeWidth':style.get('stroke-width','0'),
    'strokeOpacity':style.get('stroke-opacity','1'),
    'strokeGradient':style.get('strokeGradient',''),
    'strokeWidth':style.get('strokeWidth','0'),
    'strokeOpacity':style.get('strokeOpacity','1'),
    'filters':style.get('filter','')
    }

    @@ -456,11 +448,11 @@ def parseRect(self,node,parent):
    'height':node.get('height',0),
    'box':[],
    'fill':style.get('fill',''),
    'fill-opacity':style.get('fill-opacity',''),
    'fillOpacity':style.get('fillOpacity',''),
    'opacity':style.get('opacity',''),
    'stroke':style.get('stroke',''),
    'stroke-width':style.get('stroke-width',''),
    'stroke-opacity':style.get('stroke-opacity',''),
    'strokeWidth':style.get('strokeWidth',''),
    'strokeOpacity':style.get('strokeOpacity',''),
    'transform':node.get('transform','')
    }

    @@ -489,10 +481,10 @@ def parseArc(self,node,parent):
    'd':node.get('d',''),
    'box':list(simpletransform.computeBBox([node])),
    'fill':style.get('fill',''),
    'fill-opacity':style.get('fill-opacity',''),
    'fill-opacity':style.get('fillOpacity',''),
    'stroke':style.get('stroke',''),
    'stroke-width':style.get('stroke-width',''),
    'stroke-opacity':style.get('stroke-opacity',''),
    'stroke-width':style.get('strokeWidth',''),
    'stroke-opacity':style.get('strokeOpacity',''),
    'transform':node.get('transform','')
    }

    @@ -622,6 +614,14 @@ def repositionGroupedElements(self, box, elements):
    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):
    @@ -653,7 +653,7 @@ def templateOutput(self,templateName = '',placeholder = ''):

    if(len(template) > 0):
    content = ','.join([str(el) for el in self.json_output])
    tplResult = string.replace(template,placeholder,content);
    tplResult = string.replace(template,placeholder,content)
    return tplResult
    else:
    inkex.debug('Bonsai.templateOutput: Empty template file "'+templateName+'". Aborting.')
  4. larscwallin revised this gist Dec 21, 2012. 1 changed file with 7 additions and 0 deletions.
    7 changes: 7 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,10 @@
    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 :)
  5. larscwallin revised this gist Dec 21, 2012. 1 changed file with 189 additions and 2 deletions.
    191 changes: 189 additions & 2 deletions larscwallin.inx.bansai.template.html
    Original file line number Diff line number Diff line change
    @@ -102,20 +102,129 @@
    }

    if(node.attr.fillGradient!==''){
    switch(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!==''){
    switch(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');
    }

    }
    @@ -146,6 +255,68 @@

    },

    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) {
    @@ -154,6 +325,22 @@
    }
    }
    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;
    }
    };

  6. larscwallin revised this gist Dec 20, 2012. 1 changed file with 13 additions and 2 deletions.
    15 changes: 13 additions & 2 deletions larscwallin.inx.bansai.template.html
    Original file line number Diff line number Diff line change
    @@ -18,6 +18,7 @@
    bansai = {
    ids: {},
    labels:{},
    groups:{},
    stage:null,

    init:function(stage,svgDoc){
    @@ -62,7 +63,12 @@
    bansai.ids[node.id] = group;

    if (node.label !== '') {
    bansai.labels[node.name] = group;

    if(!bansai.labels[node.label]){
    bansai.labels[node.label] = [];
    }

    bansai.labels[node.label].push(group);
    }

    group.addTo(parent);
    @@ -117,7 +123,12 @@
    bansai.ids[node.id] = path;

    if (node.label !== '') {
    bansai.labels[node.label] = path;

    if(!bansai.labels[node.label]){
    bansai.labels[node.label] = [];
    }

    bansai.labels[node.label].push(path);
    }

    path.addTo(parent);
  7. larscwallin revised this gist Dec 19, 2012. 2 changed files with 27 additions and 15 deletions.
    6 changes: 6 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,9 @@
    Update 20121219 19:06

    Added blur filter support to the Bansai js code :)

    --------------------------------------------------------------------------------------------------

    Update 20121219

    Gradients added to json output:
    36 changes: 21 additions & 15 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -285,7 +285,7 @@ def parseElement(self,node,parent):

    def parseStyleAttribute(self,str):

    inkex.debug(self.debug_tab + 'Got style ' + str)
    #inkex.debug(self.debug_tab + 'Got style ' + str)

    rules = str.split(';')
    parsed_set = {}
    @@ -310,7 +310,7 @@ def parseStyleAttribute(self,str):
    def parseFilter(self,filter_str):
    # Split out the id from the url reference
    filter_id = self.parseUrlParam(filter_str)
    result_list = {}
    result_list = []
    tag_name = ''

    # Got a valid id?
    @@ -319,16 +319,20 @@ def parseFilter(self,filter_str):
    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'))
    inkex.debug(self.debug_tab + 'Looping filter ' + node.get('id'))
    tag_name = self.parseTagName(node.tag)

    filter = {}
    filter = {
    'svg':tag_name
    }

    # Grab all the parameters and values for the current filter
    for param,val in node.items():
    #inkex.debug(self.debug_tab + 'param ' + param + ' val ' + val)
    param = self.parseTagName(param)
    inkex.debug(self.debug_tab + 'param ' + param + ' val ' + val)
    filter[param] = val

    result_list[tag_name] = filter
    result_list.append(filter)

    return result_list

    @@ -371,7 +375,7 @@ def parsePath(self,node,parent):
    'strokeGradient':style.get('stroke-gradient',''),
    'strokeWidth':style.get('stroke-width','0'),
    'strokeOpacity':style.get('stroke-opacity','1'),
    'filter':style.get('filter',{})
    'filters':style.get('filter','')
    }

    }
    @@ -538,29 +542,29 @@ def parseGradient(self,gradient_str):
    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')
    #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)
    #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)
    #inkex.debug(self.debug_tab + 'Looping through gradient parameter attributes of ' + param)

    if(param == 'style'):
    inkex.debug(self.debug_tab + 'Parsing style ' + val)
    #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)
    #inkex.debug(self.debug_tab + 'Adding param/value : ' + param + '/' + val)
    gradient_stop[param] = val

    inkex.debug(self.debug_tab + 'Adding stop ' + gradient_stop['id'])
    #inkex.debug(self.debug_tab + 'Adding stop ' + gradient_stop['id'])
    gradient_params['stops'].append(gradient_stop)

    elif(param == 'gradientTransform'):
    @@ -569,7 +573,9 @@ def parseGradient(self,gradient_str):
    else:
    gradient_params[param] = val

    result_list[tag_name] = gradient_params
    gradient_params['svg'] = tag_name

    result_list = gradient_params

    return result_list

  8. larscwallin revised this gist Dec 19, 2012. 1 changed file with 41 additions and 7 deletions.
    48 changes: 41 additions & 7 deletions larscwallin.inx.bansai.template.html
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,6 @@
    bansai = {
    ids: {},
    labels:{},
    createBackgroundLayer:function(){return new Group().attr({x:0, y:0}).addTo(stage)},
    stage:null,

    init:function(stage,svgDoc){
    @@ -37,6 +36,10 @@
    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();

    @@ -81,6 +84,36 @@
    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!==''){
    switch(node.attr.fillGradient){
    case 'linearGradient':
    break;
    case 'radialGradient':
    break;
    }
    }

    if(node.attr.strokeGradient!==''){
    switch(node.attr.strokeGradient){
    case 'linearGradient':
    break;
    case 'radialGradient':
    break;
    }

    }

    bansai.ids[node.id] = path;

    if (node.label !== '') {
    @@ -115,18 +148,19 @@

    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'];
    */
    }
    });
  9. larscwallin revised this gist Dec 19, 2012. 2 changed files with 18 additions and 29 deletions.
    13 changes: 12 additions & 1 deletion History.txt
    Original file line number Diff line number Diff line change
    @@ -30,7 +30,18 @@ Gradients added to json output:
    'collect':'always',
    'cy':'323.79074',
    'cx':'281.42856',
    'gradientTransform':'matrix(0.96764084,-0.07697006,0.08781738,1.1040096,-19.327677,-12.015768)',
    'gradientTransform':[
    [
    0.96764083999999995,
    0.08781738,
    -19.327677000000001
    ],
    [
    -0.076970060000000007,
    1.1040095999999999,
    -12.015768
    ]
    ],
    'r':'95.714287',
    'id':'radialGradient850'
    }
    34 changes: 6 additions & 28 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -23,28 +23,6 @@
    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
    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
    - Filters
    - Gradients
    - Fonts
    - "use" references
    Thanks to Bonsai its really easy for anyone with a basic knowledge of Python,
    JavaScript and SVG to help with development :)
    """


    @@ -553,9 +531,7 @@ def parseGradient(self,gradient_str):
    #inkex.debug(self.debug_tab + 'param ' + param + ' val ' + val)
    param = self.parseTagName(param)

    if(param != 'href'):
    gradient_params[param] = val
    else:
    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.
    @@ -587,6 +563,11 @@ def parseGradient(self,gradient_str):
    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

    result_list[tag_name] = gradient_params

    @@ -626,9 +607,6 @@ def parseFont(self,node,parent):
    def parseGlyph(self,node,parent):
    pass

    def getElementFilters(self,id):
    filter = node.xpath('//svg:svg/svg:',namespaces=inkex.NSS)[0]

    def pathToObject(self,node):
    pass

  10. larscwallin revised this gist Dec 19, 2012. 2 changed files with 41 additions and 25 deletions.
    55 changes: 33 additions & 22 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -3,29 +3,40 @@ Update 20121219
    Gradients added to json output:

    {
    'strokeGradient':{
    'linearGradient':{
    'gradientUnits':'userSpaceOnUse',
    'y2':'323.79074',
    'x2':'383.35713',
    'collect':'always',
    'y1':'323.79074',
    'x1':'179.49998',
    'id':'linearGradient826'
    }
    },
    'strokeOpacity':'1',
    'fillGradient':{
    'linearGradient':{
    'gradientUnits':'userSpaceOnUse',
    'y2':'189.50504',
    'x2':'280',
    'collect':'always',
    'y1':'432.36218',
    'x1':'294.28571',
    'id':'linearGradient818'
    }
    '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':'matrix(0.96764084,-0.07697006,0.08781738,1.1040096,-19.327677,-12.015768)',
    'r':'95.714287',
    'id':'radialGradient850'
    }
    }

    --------------------------------------------------------------------------------------------------

    Update 20121218 20:50

    11 changes: 8 additions & 3 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -536,7 +536,6 @@ def parseGradient(self,gradient_str):
    tag_name = ''
    gradient_stops = []
    gradient_params = {}
    gradient_stop = {}

    # Got a valid id?
    if(gradient_use_id != ''):
    @@ -553,6 +552,7 @@ def parseGradient(self,gradient_str):
    for param,val in gradient_use.items():
    #inkex.debug(self.debug_tab + 'param ' + param + ' val ' + val)
    param = self.parseTagName(param)

    if(param != 'href'):
    gradient_params[param] = val
    else:
    @@ -566,23 +566,28 @@ def parseGradient(self,gradient_str):
    inkex.debug(self.debug_tab + 'Looping through ' + str(len(gradient_href)) + ' gradient parameter elements')

    for node in gradient_href:
    tag_name = self.parseTagName(node.tag)

    inkex.debug(self.debug_tab + 'Current parameter element ' + tag_name)
    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)


    result_list[tag_name] = gradient_params

    return result_list
  11. larscwallin revised this gist Dec 19, 2012. 1 changed file with 33 additions and 15 deletions.
    48 changes: 33 additions & 15 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -306,20 +306,25 @@ def parseElement(self,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(parts[0] == 'filter'):
    parsed_set['filter'] = self.parseFilter(parts[1])
    elif(parts[0] == 'fill' and parts[1].find('url(#') > -1):
    parsed_set['fill-gradient'] = self.parseGradient(parts[1])
    elif(parts[0] == 'stroke' and parts[1].find('url(#') > -1):
    parsed_set['stroke-gradient'] = self.parseGradient(parts[1])
    else:
    parsed_set[parts[0]] = parts[1]
    if(len(parts) > 1):

    if(parts[0] == 'filter'):
    parsed_set['filter'] = self.parseFilter(parts[1])
    elif(parts[0] == 'fill' and parts[1].find('url(#') > -1):
    parsed_set['fill-gradient'] = self.parseGradient(parts[1])
    elif(parts[0] == 'stroke' and parts[1].find('url(#') > -1):
    parsed_set['stroke-gradient'] = self.parseGradient(parts[1])
    else:
    parsed_set[parts[0]] = parts[1]

    result = parsed_set
    return result
    @@ -529,6 +534,9 @@ def parseGradient(self,gradient_str):
    gradient_href = {}
    result_list = {}
    tag_name = ''
    gradient_stops = []
    gradient_params = {}
    gradient_stop = {}

    # Got a valid id?
    if(gradient_use_id != ''):
    @@ -537,7 +545,7 @@ def parseGradient(self,gradient_str):

    tag_name = self.parseTagName(gradient_use.tag)

    gradient_params = {}
    gradient_params['stops'] = []

    #inkex.debug(self.debug_tab + 'Gradient ' + tag_name)

    @@ -548,22 +556,32 @@ def parseGradient(self,gradient_str):
    if(param != 'href'):
    gradient_params[param] = val
    else:

    # 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:
    tag_name = self.parseTagName(node.tag)

    inkex.debug(self.debug_tab + 'Current parameter element ' + tag_name)

    for param,val in node.items():

    for param,val in node:
    param = self.parseTagName(param)

    if(param in gradient_params):
    pass
    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:
    param = self.parseTagName(param)

    gradient_params[param] = val
    gradient_stop[param] = val

    gradient_params['stops'].append(gradient_stop)

    result_list[tag_name] = gradient_params

  12. larscwallin revised this gist Dec 19, 2012. 1 changed file with 6 additions and 5 deletions.
    11 changes: 6 additions & 5 deletions Bansai!
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    Bansai! version 0.00001
    Bansai!

    IMPORTANT - ALL LAYERS MUST BE HIDDEN AND THEN DISPLAYED IN THE LAYERS LIST TO
    GET THE NEEDED STYLE ATTRIBUTE WHICH IS USED BY BANSAI!
    @@ -14,17 +14,18 @@ GET THE NEEDED STYLE ATTRIBUTE WHICH IS USED BY BANSAI!
    - 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
    - Filters
    - Gradients
    - Fonts
    - "use" references
    - 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 :)
  13. larscwallin revised this gist Dec 19, 2012. 2 changed files with 96 additions and 21 deletions.
    31 changes: 30 additions & 1 deletion History.txt
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,35 @@
    Update 20121219

    Gradients added to json output:

    {
    'strokeGradient':{
    'linearGradient':{
    'gradientUnits':'userSpaceOnUse',
    'y2':'323.79074',
    'x2':'383.35713',
    'collect':'always',
    'y1':'323.79074',
    'x1':'179.49998',
    'id':'linearGradient826'
    }
    },
    'strokeOpacity':'1',
    'fillGradient':{
    'linearGradient':{
    'gradientUnits':'userSpaceOnUse',
    'y2':'189.50504',
    'x2':'280',
    'collect':'always',
    'y1':'432.36218',
    'x1':'294.28571',
    'id':'linearGradient818'
    }
    }

    Update 20121218 20:50

    Implemented inclusion of filters.
    Implemented inclusion of filters:

    {
    'filter':{
    86 changes: 66 additions & 20 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -314,6 +314,10 @@ def parseStyleAttribute(self,str):

    if(parts[0] == 'filter'):
    parsed_set['filter'] = self.parseFilter(parts[1])
    elif(parts[0] == 'fill' and parts[1].find('url(#') > -1):
    parsed_set['fill-gradient'] = self.parseGradient(parts[1])
    elif(parts[0] == 'stroke' and parts[1].find('url(#') > -1):
    parsed_set['stroke-gradient'] = self.parseGradient(parts[1])
    else:
    parsed_set[parts[0]] = parts[1]

    @@ -322,7 +326,7 @@ def parseStyleAttribute(self,str):

    def parseFilter(self,filter_str):
    # Split out the id from the url reference
    filter_id = filter_str.split('url(#')[1].split(')')[0]
    filter_id = self.parseUrlParam(filter_str)
    result_list = {}
    tag_name = ''

    @@ -333,8 +337,8 @@ def parseFilter(self,filter_str):

    for node in filters:
    #inkex.debug(self.debug_tab + 'Looping filter ' + node.get('id'))
    tag_name = node.tag
    tag_name = tag_name.split('}')[1]
    tag_name = self.parseTagName(node.tag)

    filter = {}
    # Grab all the parameters and values for the current filter
    for param,val in node.items():
    @@ -377,9 +381,11 @@ def parsePath(self,node,parent):
    'd':node.get('d',''),
    'attr':{
    'fillColor':style.get('fill',''),
    'fillGradient':style.get('fill-gradient',''),
    'fillOpacity':style.get('fill-opacity','1'),
    'opacity':style.get('opacity','1'),
    'strokeColor':style.get('stroke',''),
    'strokeGradient':style.get('stroke-gradient',''),
    'strokeWidth':style.get('stroke-width','0'),
    'strokeOpacity':style.get('stroke-opacity','1'),
    'filter':style.get('filter',{})
    @@ -515,8 +521,53 @@ def parseDef(self,node,parent):
    pass


    def parseGradient(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 = ''

    # 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 = {}

    #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'):
    gradient_params[param] = val
    else:
    # 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)

    for node in gradient_href:

    for param,val in node:

    if(param in gradient_params):
    pass
    else:
    param = self.parseTagName(param)

    gradient_params[param] = val


    result_list[tag_name] = 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' }
    @@ -544,21 +595,6 @@ def parseGradient(self,node,parent):
    Either 'userSpace' or 'boundingBox'.
    """

    def parseFilterBlur(self,node,parent):
    pass

    """
    blurRadius, mixed number, 0 - Infinity. If omitted, defaultValue is 1.
    Example
    var f = new filter.Blur();
    var f = new filter.Blur(1);
    """

    def parseFont(self,node,parent):
    @@ -576,6 +612,16 @@ def pathToObject(self,node):
    def repositionGroupedElements(self, box, elements):
    pass

    def parseUrlParam(self,url):
    return url.split('url(#')[1].split(')')[0]

    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
  14. larscwallin revised this gist Dec 18, 2012. 1 changed file with 45 additions and 2 deletions.
    47 changes: 45 additions & 2 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,15 +1,59 @@
    Update 20121218 20:50

    Implemented inclusion of filters. No frontend JS code yet.
    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
    @@ -26,7 +70,6 @@ This means that you can preserve your Inkscape layer/shape/group naming in you s
    You JS console will also output all the contents of these two indexes.



    --------------------------------------------------------------------------------------------------

    Update 20121214 15:23
  15. larscwallin revised this gist Dec 18, 2012. 2 changed files with 42 additions and 2 deletions.
    4 changes: 4 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,7 @@
    Update 20121218 20:50

    Implemented inclusion of filters. No frontend JS code yet.

    Update 20121216 23:07

    Fixed crash in py code when retrieving fill color for paths.
    40 changes: 38 additions & 2 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -155,6 +155,8 @@ def effect(self):
    filename = ''
    success = False

    #inkex.debug("filter:url(#g4567)".split('filter:url(#')[1].split(')'))

    self.getselected()

    if(self.selected.__len__() > 0):
    @@ -309,11 +311,41 @@ def parseStyleAttribute(self,str):
    result = ''
    for rule in rules:
    parts = rule.split(':')
    parsed_set[parts[0]] = parts[1]

    if(parts[0] == 'filter'):
    parsed_set['filter'] = self.parseFilter(parts[1])
    else:
    parsed_set[parts[0]] = parts[1]

    result = parsed_set
    return result

    def parseFilter(self,filter_str):
    # Split out the id from the url reference
    filter_id = filter_str.split('url(#')[1].split(')')[0]
    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 = node.tag
    tag_name = tag_name.split('}')[1]
    filter = {}
    # Grab all the parameters and values for the current filter
    for param,val in node.items():
    #inkex.debug(self.debug_tab + 'param ' + param + ' val ' + val)
    filter[param] = val

    result_list[tag_name] = filter

    return result_list



    def parsePath(self,node,parent):

    @@ -349,7 +381,8 @@ def parsePath(self,node,parent):
    'opacity':style.get('opacity','1'),
    'strokeColor':style.get('stroke',''),
    'strokeWidth':style.get('stroke-width','0'),
    'strokeOpacity':style.get('stroke-opacity','1')
    'strokeOpacity':style.get('stroke-opacity','1'),
    'filter':style.get('filter',{})
    }

    }
    @@ -534,6 +567,9 @@ def parseFont(self,node,parent):
    def parseGlyph(self,node,parent):
    pass

    def getElementFilters(self,id):
    filter = node.xpath('//svg:svg/svg:',namespaces=inkex.NSS)[0]

    def pathToObject(self,node):
    pass

  16. larscwallin revised this gist Dec 16, 2012. 1 changed file with 13 additions and 0 deletions.
    13 changes: 13 additions & 0 deletions larscwallin.inx.bansai.template.html
    Original file line number Diff line number Diff line change
    @@ -115,6 +115,19 @@

    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'];
    */
    }
    });

  17. larscwallin revised this gist Dec 16, 2012. 2 changed files with 56 additions and 2 deletions.
    4 changes: 4 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,7 @@
    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.
    54 changes: 52 additions & 2 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -237,6 +237,8 @@ def effect(self):
    inkex.debug('Unable to write to file "' + filename + '"')

    #inkex.debug(self.debug_tab + ','.join([str(el) for el in self.json_output]))
    def normalizeSVG():
    pass

    def parseGroup(self,node,parent):
    #inkex.debug(self.debug_tab + 'Parsing group' + node.get('id'))
    @@ -342,8 +344,9 @@ def parsePath(self,node,parent):
    'path':path_array,
    'd':node.get('d',''),
    'attr':{
    'fillColor':style['fill'],
    'fillColor':style.get('fill',''),
    'fillOpacity':style.get('fill-opacity','1'),
    'opacity':style.get('opacity','1'),
    'strokeColor':style.get('stroke',''),
    'strokeWidth':style.get('stroke-width','0'),
    'strokeOpacity':style.get('stroke-opacity','1')
    @@ -428,6 +431,7 @@ def parseRect(self,node,parent):
    'box':[],
    'fill':style.get('fill',''),
    'fill-opacity':style.get('fill-opacity',''),
    'opacity':style.get('opacity',''),
    'stroke':style.get('stroke',''),
    'stroke-width':style.get('stroke-width',''),
    'stroke-opacity':style.get('stroke-opacity',''),
    @@ -477,6 +481,53 @@ def parseArc(self,node,parent):
    def parseDef(self,node,parent):
    pass


    def parseGradient(self,node,parent):
    pass

    """
    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 parseFilterBlur(self,node,parent):
    pass

    """
    blurRadius, mixed number, 0 - Infinity. If omitted, defaultValue is 1.
    Example
    var f = new filter.Blur();
    var f = new filter.Blur(1);
    """

    def parseFont(self,node,parent):
    pass

    @@ -551,4 +602,3 @@ def run(self):
    effect = Bansai()
    effect.affect(output=False)


  18. larscwallin revised this gist Dec 16, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion larscwallin.inx.bansai.template.html
    Original file line number Diff line number Diff line change
    @@ -59,7 +59,7 @@
    bansai.ids[node.id] = group;

    if (node.label !== '') {
    bansai.labels[node.label] = group;
    bansai.labels[node.name] = group;
    }

    group.addTo(parent);
  19. larscwallin revised this gist Dec 16, 2012. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,7 @@
    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
  20. larscwallin revised this gist Dec 16, 2012. 1 changed file with 85 additions and 74 deletions.
    159 changes: 85 additions & 74 deletions larscwallin.inx.bansai.template.html
    Original file line number Diff line number Diff line change
    @@ -12,99 +12,110 @@
    stage = document.getElementById('stage');

    bonsai.run(stage, {
    lookup: { ids: {},labels:{}},
    code: function() {

    var svgDoc = {/*bonsai_content*/};
    var backgroundLayer = new Group().attr({x:0, y:0}).addTo(stage);

    svgDoc.elements.forEach(function(el) {
    if(el.svg === 'g'){
    addGroup(el,backgroundLayer);
    }else{
    addPath(el,backgroundLayer);
    }
    });

    console.log("Dump of indexed SVG label attributes:" + objToString(stage.options.lookup.labels));
    console.log("Dump of indexed SVG id attributes:" + objToString(stage.options.lookup.ids));

    function addGroup(node,parent){
    var group = new Group();
    code: function() {

    node.elements.forEach(function(el){
    if(el.svg === 'g'){
    addGroup(el,group);
    }else if(el.svg === 'path'){
    addPath(el,group);
    bansai = {
    ids: {},
    labels:{},
    createBackgroundLayer:function(){return new Group().attr({x:0, y:0}).addTo(stage)},
    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));
    },

    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);
    }
    });

    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);
    }

    stage.options.lookup.ids[node.id] = group;

    if (node.label !== '') {
    stage.options.lookup.labels[node.label] = group;
    }

    group.addTo(parent);

    }
    bansai.ids[node.id] = group;

    function addPath(node,parent){
    path = new Path(node.path).attr({
    fillColor: node.attr.fillColor,
    strokeColor: node.attr.strokeColor,
    strokeWidth: node.attr.strokeWidth
    });
    if (node.label !== '') {
    bansai.labels[node.label] = 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]);
    path.attr('matrix',m);
    }
    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);
    }

    stage.options.lookup.ids[node.id] = path;
    bansai.ids[node.id] = path;

    if (node.label !== '') {
    stage.options.lookup.labels[node.label] = path;
    }
    if (node.label !== '') {
    bansai.labels[node.label] = path;
    }

    path.addTo(parent);
    }
    path.addTo(parent);
    },

    function addFlowRoot(node,parent){
    addFlowRoot:function (node,parent){

    }
    },

    function addFlowPara(node,parent){
    addFlowPara:function (node,parent){

    }
    },

    function addFlowRegion(node,parent){
    addFlowRegion:function(node,parent){

    }
    },

    function objToString (obj) {
    var str = '';
    for (var p in obj) {
    if (obj.hasOwnProperty(p)) {
    str += p + '::' + obj[p] + '\n';
    objToString:function(obj) {
    var str = '';
    for (var p in obj) {
    if (obj.hasOwnProperty(p)) {
    str += p + '::' + obj[p] + '\n';
    }
    }
    return "\n" + str;
    }
    return "\n" + str;
    }
    }
    };

    var svgDoc = {/*bonsai_content*/};
    bansai.init(stage,svgDoc);
    }
    });

    </script>
  21. larscwallin revised this gist Dec 14, 2012. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -9,8 +9,12 @@ 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
  22. larscwallin revised this gist Dec 14, 2012. 2 changed files with 44 additions and 8 deletions.
    17 changes: 17 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,25 @@
    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.

    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.
    35 changes: 27 additions & 8 deletions larscwallin.inx.bansai.template.html
    Original file line number Diff line number Diff line change
    @@ -12,7 +12,7 @@
    stage = document.getElementById('stage');

    bonsai.run(stage, {

    lookup: { ids: {},labels:{}},
    code: function() {

    var svgDoc = {/*bonsai_content*/};
    @@ -24,18 +24,14 @@
    }else{
    addPath(el,backgroundLayer);
    }
    },
    {
    lookup:{
    ids:{},
    labels:{}
    }
    });

    console.log("Dump of indexed SVG label attributes:" + objToString(stage.options.lookup.labels));
    console.log("Dump of indexed SVG id attributes:" + objToString(stage.options.lookup.ids));

    function addGroup(node,parent){
    var group = new Group();


    node.elements.forEach(function(el){
    if(el.svg === 'g'){
    addGroup(el,group);
    @@ -51,6 +47,13 @@
    m.translate(node.transform[0][2],node.transform[1][2]);
    group.attr('matrix',m);
    }

    stage.options.lookup.ids[node.id] = group;

    if (node.label !== '') {
    stage.options.lookup.labels[node.label] = group;
    }

    group.addTo(parent);

    }
    @@ -69,6 +72,13 @@
    m.translate(node.transform[0][2],node.transform[1][2]);
    path.attr('matrix',m);
    }

    stage.options.lookup.ids[node.id] = path;

    if (node.label !== '') {
    stage.options.lookup.labels[node.label] = path;
    }

    path.addTo(parent);
    }

    @@ -84,6 +94,15 @@

    }

    function objToString (obj) {
    var str = '';
    for (var p in obj) {
    if (obj.hasOwnProperty(p)) {
    str += p + '::' + obj[p] + '\n';
    }
    }
    return "\n" + str;
    }
    }

    });
  23. larscwallin revised this gist Dec 14, 2012. 3 changed files with 130 additions and 96 deletions.
    5 changes: 5 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,8 @@
    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.
    127 changes: 31 additions & 96 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,7 @@
    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')
    @@ -110,96 +111,6 @@ class Bansai(inkex.Effect):
    parsing_context = ''
    parse_stack = []

    # Proof of concept. This should of course be loaded from file later...
    template = """<!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() {
    var svgDoc = /*larscwallin_inx_elements_to_json_output*/;
    var backgroundLayer = new Group().attr({x:0, y:0}).addTo(stage);
    svgDoc.elements.forEach(function(el) {
    if(el.svg === 'g'){
    addGroup(el,backgroundLayer);
    }else{
    addPath(el,backgroundLayer);
    }
    });
    function addGroup(node,parent){
    var group = new Group();
    node.elements.forEach(function(el){
    if(el.svg === 'g'){
    addGroup(el,group);
    }else if(el.svg === 'path'){
    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);
    }
    group.addTo(parent);
    }
    function addPath(node,parent){
    path = new Path(node.path).attr({
    fillColor: node.attr.fillColor,
    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);
    }
    path.addTo(parent);
    }
    function addFlowRoot(node,parent){
    }
    function addFlowPara(node,parent){
    }
    function addFlowRegion(node,parent){
    }
    }
    });
    </script>
    </body>
    </html>
    """

    def __init__(self):
    """
    Constructor.
    @@ -316,8 +227,8 @@ def effect(self):

    # 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, 'elements-to-json-'+time_stamp+'.html')
    content = self.templateOutput()
    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):
    @@ -583,13 +494,35 @@ def viewOutput(self,url):
    vwswli.url = url
    vwswli.start()

    def templateOutput(self):
    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


    content = ','.join([str(el) for el in self.json_output])
    FILE = open(templateName,'r')

    tplContent = string.replace(self.template,'/*larscwallin_inx_elements_to_json_output*/',content);
    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

    return tplContent

    def saveToFile(self,content,filename):

    @@ -600,6 +533,7 @@ def saveToFile(self,content,filename):
    FILE.close()
    return True
    else:
    inkex.debug('Bonsai.templateOutput: Unable to open output file "'+filename+'". Aborting.')
    return False


    @@ -612,6 +546,7 @@ def run(self):
    webbrowser.open(self.url)



    # Create effect instance and apply it.
    effect = Bansai()
    effect.affect(output=False)
    94 changes: 94 additions & 0 deletions larscwallin.inx.bansai.template.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,94 @@
    <!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() {

    var svgDoc = {/*bonsai_content*/};
    var backgroundLayer = new Group().attr({x:0, y:0}).addTo(stage);

    svgDoc.elements.forEach(function(el) {
    if(el.svg === 'g'){
    addGroup(el,backgroundLayer);
    }else{
    addPath(el,backgroundLayer);
    }
    },
    {
    lookup:{
    ids:{},
    labels:{}
    }
    });

    function addGroup(node,parent){
    var group = new Group();


    node.elements.forEach(function(el){
    if(el.svg === 'g'){
    addGroup(el,group);
    }else if(el.svg === 'path'){
    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);
    }
    group.addTo(parent);

    }

    function addPath(node,parent){
    path = new Path(node.path).attr({
    fillColor: node.attr.fillColor,
    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);
    }
    path.addTo(parent);
    }

    function addFlowRoot(node,parent){

    }

    function addFlowPara(node,parent){

    }

    function addFlowRegion(node,parent){

    }

    }

    });

    </script>

    </body>
    </html>
  24. larscwallin revised this gist Dec 13, 2012. 2 changed files with 8 additions and 3 deletions.
    4 changes: 4 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,7 @@
    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.
    7 changes: 4 additions & 3 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -316,7 +316,7 @@ def effect(self):

    # 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')
    filename = os.path.join(self.where, 'elements-to-json-'+time_stamp+'.html')
    content = self.templateOutput()
    success = self.saveToFile(content,filename)

    @@ -340,13 +340,14 @@ def parseGroup(self,node,parent):
    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':list(box),
    'box':box,
    'elements':[]
    }

    @@ -449,7 +450,7 @@ def parsePath(self,node,parent):
    else:
    path['path'] = simplepath.formatPath(path_array)

    path['box'] = list(path['box'])
    path['box'] = list(path['box']) if path['box'] != None else []
    parent.append(path)


  25. larscwallin revised this gist Dec 13, 2012. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions History.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    Update 20121213 10:21

    I'm such a dork. I forgot to add transform support for Path elements.
    Fixed now though :)
  26. larscwallin revised this gist Dec 13, 2012. 1 changed file with 18 additions and 9 deletions.
    27 changes: 18 additions & 9 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -161,12 +161,21 @@ class Bansai(inkex.Effect):
    group.addTo(parent);
    }
    function addPath(el,parent){
    path = new Path(el.path).attr({
    fillColor: el.attr.fillColor,
    strokeColor: el.attr.strokeColor,
    strokeWidth: el.attr.strokeWidth
    }).addTo(parent);
    function addPath(node,parent){
    path = new Path(node.path).attr({
    fillColor: node.attr.fillColor,
    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);
    }
    path.addTo(parent);
    }
    function addFlowRoot(node,parent){
    @@ -307,7 +316,7 @@ def effect(self):

    # 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, 'elements-to-json-'+time_stamp+'.html')
    filename = os.path.join(self.where, 'bansai-'+time_stamp+'.html')
    content = self.templateOutput()
    success = self.saveToFile(content,filename)

    @@ -398,7 +407,7 @@ def parsePath(self,node,parent):

    style = node.get('style')
    style = self.parseStyleAttribute(style)

    transform = simpletransform.parseTransform(node.get('transform',''))
    path_array = simplepath.parsePath(node.get('d'))

    #inkex.debug(style)
    @@ -417,7 +426,7 @@ def parsePath(self,node,parent):
    'svg':'path',
    'label':str(node.get(inkex.addNS('label', 'inkscape'),'')),
    'box':list(simpletransform.computeBBox([node])),
    'transform':node.get('transform',''),
    'transform':transform,
    'path':path_array,
    'd':node.get('d',''),
    'attr':{
  27. larscwallin revised this gist Dec 13, 2012. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions Bansai!
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,8 @@
    Bansai! version 0.00001

    IMPORTANT - ALL LAYERS MUST BE HIDDEN AND THEN DISPLAYED IN THE LAYERS LIST TO
    GET THE NEEDED STYLE ATTRIBUTE WHICH IS USED BY 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 ;)
  28. larscwallin revised this gist Dec 13, 2012. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -319,7 +319,7 @@ def effect(self):
    #inkex.debug(self.debug_tab + ','.join([str(el) for el in self.json_output]))

    def parseGroup(self,node,parent):
    inkex.debug(self.debug_tab + 'Parsing group' + node.get('id'))
    #inkex.debug(self.debug_tab + 'Parsing group' + node.get('id'))
    self.debug_tab += ' '

    self.parsing_context = 'g'
    @@ -368,7 +368,7 @@ def parseElement(self,node,parent):

    id = node.get('id')

    inkex.debug(self.debug_tab + 'Got "' + tag_name + '" element ' + id);
    #inkex.debug(self.debug_tab + 'Got "' + tag_name + '" element ' + id);

    if(tag_name == 'g'):
    self.parseGroup(node,parent)
  29. larscwallin revised this gist Dec 13, 2012. 4 changed files with 73 additions and 7 deletions.
    1 change: 0 additions & 1 deletion A-Readme
    Original file line number Diff line number Diff line change
    @@ -1 +0,0 @@
    Bansai!
    27 changes: 27 additions & 0 deletions Bansai!
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@
    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.

    * 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

    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
    - Filters
    - Gradients
    - Fonts
    - "use" references

    Thanks to Bonsai its really easy for anyone with a basic knowledge of Python,
    JavaScript and SVG to help with development :)
    2 changes: 1 addition & 1 deletion larscwallin.inx.bansai.inx
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@
    <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?">true</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>
    50 changes: 45 additions & 5 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -10,11 +10,43 @@
    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.
    * 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
    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
    - Filters
    - Gradients
    - Fonts
    - "use" references
    Thanks to Bonsai its really easy for anyone with a basic knowledge of Python,
    JavaScript and SVG to help with development :)
    """


    class SVGElement():
    id=''
    label=''
    @@ -61,8 +93,12 @@ class SVGGroup(SVGElement):
    def __init__(self,node = None):
    SVGElement.__init__(self)

    """
    This is where the actual fun stuff starts
    """

    # Effect main class
    class ElementToJSON(inkex.Effect):
    class Bansai(inkex.Effect):
    json_output = []
    debug_tab =' '
    svg_doc = None
    @@ -74,7 +110,7 @@ class ElementToJSON(inkex.Effect):
    parsing_context = ''
    parse_stack = []

    # This should of course be loaded from file later...
    # Proof of concept. This should of course be loaded from file later...
    template = """<!DOCTYPE html>
    <html>
    <head>
    @@ -160,14 +196,18 @@ 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 = True,
    type = 'inkbool', dest = 'reposition', default = False,
    help = 'Reposition elements to 0,0?')

    self.OptionParser.add_option('--bonsaistyle', action = 'store',
    @@ -563,7 +603,7 @@ def run(self):


    # Create effect instance and apply it.
    effect = ElementToJSON()
    effect = Bansai()
    effect.affect(output=False)


  30. larscwallin created this gist Dec 12, 2012.
    1 change: 1 addition & 0 deletions A-Readme
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    Bansai!
    20 changes: 20 additions & 0 deletions larscwallin.inx.bansai.inx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    <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?">true</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>
    569 changes: 569 additions & 0 deletions larscwallin.inx.bansai.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,569 @@
    #!/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')

    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)

    # Effect main class
    class ElementToJSON(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 = []

    # This should of course be loaded from file later...
    template = """<!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() {
    var svgDoc = /*larscwallin_inx_elements_to_json_output*/;
    var backgroundLayer = new Group().attr({x:0, y:0}).addTo(stage);
    svgDoc.elements.forEach(function(el) {
    if(el.svg === 'g'){
    addGroup(el,backgroundLayer);
    }else{
    addPath(el,backgroundLayer);
    }
    });
    function addGroup(node,parent){
    var group = new Group();
    node.elements.forEach(function(el){
    if(el.svg === 'g'){
    addGroup(el,group);
    }else if(el.svg === 'path'){
    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);
    }
    group.addTo(parent);
    }
    function addPath(el,parent){
    path = new Path(el.path).attr({
    fillColor: el.attr.fillColor,
    strokeColor: el.attr.strokeColor,
    strokeWidth: el.attr.strokeWidth
    }).addTo(parent);
    }
    function addFlowRoot(node,parent){
    }
    function addFlowPara(node,parent){
    }
    function addFlowRegion(node,parent){
    }
    }
    });
    </script>
    </body>
    </html>
    """

    def __init__(self):
    """
    Constructor.
    """

    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 = True,
    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

    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)

    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, 'elements-to-json-'+time_stamp+'.html')
    content = self.templateOutput()
    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 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)

    group = {
    'id':id,
    'name':label,
    'svg':'g',
    'transform':transform,
    'box':list(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):
    rules = str.split(';')
    parsed_set = {}
    result = ''
    for rule in rules:
    parts = rule.split(':')
    parsed_set[parts[0]] = parts[1]

    result = parsed_set
    return result


    def parsePath(self,node,parent):

    #self.parsing_context = 'path'

    style = node.get('style')
    style = self.parseStyleAttribute(style)

    path_array = simplepath.parsePath(node.get('d'))

    #inkex.debug(style)

    ## if(self.bonsaistyle):
    ## inkex.debug('Bonsai style!')
    ## for attr in style:
    ## inkex.debug('Attr ' + attr)
    ## camelAttr = attr.split('-')
    ## if(len(camelAttr) == 2):
    ## attr = camelAttr[0] + camelAttr[1].title()
    ## inkex.debug('Camel version ' + attr)

    path = {
    'id':node.get('id'),
    'svg':'path',
    'label':str(node.get(inkex.addNS('label', 'inkscape'),'')),
    'box':list(simpletransform.computeBBox([node])),
    'transform':node.get('transform',''),
    'path':path_array,
    'd':node.get('d',''),
    'attr':{
    'fillColor':style['fill'],
    'fillOpacity':style.get('fill-opacity','1'),
    'strokeColor':style.get('stroke',''),
    'strokeWidth':style.get('stroke-width','0'),
    'strokeOpacity':style.get('stroke-opacity','1')
    }

    }




    #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'])
    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',''),
    'fill-opacity':style.get('fill-opacity',''),
    'stroke':style.get('stroke',''),
    'stroke-width':style.get('stroke-width',''),
    'stroke-opacity':style.get('stroke-opacity',''),
    '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('fill-opacity',''),
    'stroke':style.get('stroke',''),
    'stroke-width':style.get('stroke-width',''),
    'stroke-opacity':style.get('stroke-opacity',''),
    '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 parseFont(self,node,parent):
    pass

    def parseGlyph(self,node,parent):
    pass

    def pathToObject(self,node):
    pass

    def repositionGroupedElements(self, box, elements):
    pass

    def viewOutput(self,url):
    vwswli = VisitWebSiteWithoutLockingInkscape()
    vwswli.url = url
    vwswli.start()

    def templateOutput(self):

    content = ','.join([str(el) for el in self.json_output])

    tplContent = string.replace(self.template,'/*larscwallin_inx_elements_to_json_output*/',content);

    return tplContent

    def saveToFile(self,content,filename):

    FILE = open(filename,'w')

    if(FILE):
    FILE.write(content)
    FILE.close()
    return True
    else:
    return False


    class VisitWebSiteWithoutLockingInkscape(threading.Thread):
    url = ''
    def __init__(self):
    threading.Thread.__init__ (self)

    def run(self):
    webbrowser.open(self.url)


    # Create effect instance and apply it.
    effect = ElementToJSON()
    effect.affect(output=False)