Hi, I was wondering how can I ungroup all layers inside of the SVG or move the paths out from the layers and then delete the empty layers. I'll need the SVG file with all the paths at the top level for further processing. Does someone have any ideas on solving this? Thanks!
For example
Input: SVG has several layers in parallel
paper #layer with path inside, all paths stroke are in #4c0000
polygon #layer with path inside, all paths stroke are in #4c0000
canvas #layer with path inside, all paths stroke are in #4c0000
Output: SVG with all the paths that were previously inside of the paper and polygon layer but no layers.
I was trying to use color to select the path, but there are error messages.
def effect(self):
all_layers = self.svg.xpath('//svg:g[@inkscape:groupmode="layer"]')
for p in all_layers:
if p.style["stroke"] == "#4c0000":
self.svg.add(p)
Please note I am a hobby programmer :) My comments might be corrected by real python programmers !
Layers themselves are not part of the svg specification. However they are a common feature of graphics software, something a user expects.
It's a common problem - how do you add add extra functionality to a an XML format without breaking the file in other programs which may open it. For example a web browser ?
At the top of an Inkscape svg file you will see this :
These are 'namespaces' and also extra data to be added to the svg file for Inscape to read, but which is ignored by other programs.
For making diagram connectors for example Inkscape uses inkscape:connector-type
--------------
Layers in Inkscape are groups which contain an extra attribute in the Inkscape namespace:
inkscape:groupmode="layer"
To toggle groups / layers between being groups and layers
# Get the current selection, this could be on canvas or in the layer / object list selection_list = self.svg.selected # If nothing is selected return if len(selection_list) < 1: return for item in selection_list: # TAG gives the element tag if item.TAG == 'g': # 'get' returns None if attribute not found group_mode = item.get('inkscape:groupmode') # If we find a layer convert to group if group_mode == 'layer': item.set('inkscape:groupmode', 'group') # If we find a group with groupmode group elif group_mode == 'group': item.set('inkscape:groupmode', 'layer') # If we do not find any groupmode attribute elif not group_mode: # None is tested as boolean False by if inkex.errormsg('No inkscape:groupmode found') inkex.errormsg('Coverted To Layer') item.set('inkscape:groupmode', 'layer')
--------------------
(Do not use the word 'object' in python for objects - it is used by the python language for other purposes.)
SVG objects / elements in the extension system are etree objects.
An object can only be in one place at any one time.
So to move an object from location to another - you can use target_item.append()
Where target_item is the object you want to be the parent of the object your are moving
for example to move an object to the svg root from a group anywhere in the document:
for item in selection_list: # TAG gives the element tag if item.TAG == 'g': # Just get immediate children for child in item.getchildren(): inkex.errormsg(child) if child.style['fill'] == '#ff00ff': # Append to svg root self.svg.append(child)
-------------------------------------------------
One thing to note is that svg does not have 'z-order' just paint order based upon the order the elements appear in the document text.
So you may find that using .append() your objects are in reverse order.
copy_list = [] reverse_order = True
for item in selection_list: # TAG gives the element tag if item.TAG == 'g': for child in item.getchildren(): copy_list.append(child)
inkex.errormsg(copy_list) inkex.errormsg('-----------------') if reverse_order: copy_list.reverse() inkex.errormsg(copy_list)
for item in copy_list: self.svg.append(item)
You can change reverse_order to False :)
-------------------------------------------------
In addition to .append() you can also use .addnext() and .addprevious() to append as siblings etc
-------------------------------------------------
This little piece of code can be run on any object to see the methods / attributes which are available
def get_attributes(self): """ Returns a string containing all object attributes - One attribute per line """ attribute_string = 'test' for att in dir(self): try: attribute = (att, getattr(self, att)) attribute_string = attribute_string + str(attribute) + '\n' except: None return attribute_string
Thanks for the detailed explanation! You really help me a lot!!! :) I was able to ungroup using self.svg.append() approach.
But I found after I process the SVG by my program (1. delete layers, 2. convert group mode to Group, 3. ungroup layers, and 4. delete the empty layers.), the path 'd' data changed although it looks the same visually.
d="M 2226.989403,893.712661 L 2230.158786,1138.848680 L 2475.294814,1135.679296 L 2472.125421,890.543268 L 2226.989403,893.712661 z"
style="fill:none;stroke:#4C0000"
id="path159" />
The same path but its property 'd' converted from relative coordinates to absolute coordinates. I wonder if it is possible to retain the original data format because all the svg data processed by my Program 1 will be processed by my Program 2. Program 2 has already finished, but it only works for processing the original data format. When I run the Program 2 with the Program 1 processed svg data, I got error messages.
I'm not 100% sure why this is happening. Sometimes Inkscape will do this is there are transforms at the layer / group or object level.
You should however be able to use element.path.to_absolute() or element.path.to_relative() to go back and forth ?
I've only used this a couple of times:
from copy import deepcopy
for item in selection_list:
paths = item.xpath('.//svg:path')
for path in paths:
path_copy = deepcopy(path)
absolute_path = path_copy.path.to_absolute()
path_copy.set('d', absolute_path)
self.svg.append(path_copy)
deepcopy is part of the python copy standard library ( is part of python as standard )
You should be able to compare the paths you have in the original object to the copies in the root.
Thanks, inklinea, I just found it a bit weird that the path data 'd' format covent between the relative and absolute back and forth. I found it is not because of ungroup process.
I might need to modify my Program 2 to make it more robust so that it can parse both data formats. I haven't used path.to_absolute() and path.to_relative() before, but I'll try, thanks!
Hi, I was wondering why whey I run the code in this way below, I got the error message said get_attributes() is not defined. Thanks!
class MakeRedExtension(inkex.EffectExtension):
def get_attributes(self):
""" Returns a string containing all object attributes
- One attribute per line
"""
attribute_string = 'test'
for att in dir(self):
try:
attribute = (att, getattr(self, att))
attribute_string = attribute_string + str(attribute) + '\n'
except:
None
return attribute_string
def effect(self):
inkex.errormsg(get_attributes(Layer))
if __name__ == '__main__':
MakeRedExtension().run()
When I put the functions inside of the main inkex.EffectExtension class, the highlights indicate the get_attributes is not defined. But when I put the functions outside of the main inkex.EffectExtesion class, it works. I was wonder is it better to put all the self-defined functions outside of the main class. Previously, I put all my functions inside of the main class.
Hi, I was wondering how can I ungroup all layers inside of the SVG or move the paths out from the layers and then delete the empty layers. I'll need the SVG file with all the paths at the top level for further processing. Does someone have any ideas on solving this? Thanks!
For example
Input: SVG has several layers in parallel
Output: SVG with all the paths that were previously inside of the paper and polygon layer but no layers.
I was trying to use color to select the path, but there are error messages.
Please note I am a hobby programmer :) My comments might be corrected by real python programmers !
Layers themselves are not part of the svg specification.
However they are a common feature of graphics software, something a user expects.
It's a common problem - how do you add add extra functionality to a an XML format without breaking the file in other programs which may open it. For example a web browser ?
At the top of an Inkscape svg file you will see this :
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
These are 'namespaces' and also extra data to be added to the svg file for Inscape to read, but which is ignored by other programs.
For making diagram connectors for example Inkscape uses
inkscape:connector-type
--------------
Layers in Inkscape are groups which contain an extra attribute in the Inkscape namespace:
inkscape:groupmode="layer"
To toggle groups / layers between being groups and layers
# Get the current selection, this could be on canvas or in the layer / object list
selection_list = self.svg.selected
# If nothing is selected return
if len(selection_list) < 1:
return
for item in selection_list:
# TAG gives the element tag
if item.TAG == 'g':
# 'get' returns None if attribute not found
group_mode = item.get('inkscape:groupmode')
# If we find a layer convert to group
if group_mode == 'layer':
item.set('inkscape:groupmode', 'group')
# If we find a group with groupmode group
elif group_mode == 'group':
item.set('inkscape:groupmode', 'layer')
# If we do not find any groupmode attribute
elif not group_mode: # None is tested as boolean False by if
inkex.errormsg('No inkscape:groupmode found')
inkex.errormsg('Coverted To Layer')
item.set('inkscape:groupmode', 'layer')
--------------------
(Do not use the word 'object' in python for objects - it is used by the python language for other purposes.)
SVG objects / elements in the extension system are etree objects.
An object can only be in one place at any one time.
So to move an object from location to another - you can use
target_item.append()
Where target_item is the object you want to be the parent of the object your are moving
for example to move an object to the svg root from a group anywhere in the document:
for item in selection_list:
# TAG gives the element tag
if item.TAG == 'g':
# Just get immediate children
for child in item.getchildren():
inkex.errormsg(child)
if child.style['fill'] == '#ff00ff':
# Append to svg root
self.svg.append(child)
-------------------------------------------------
One thing to note is that svg does not have 'z-order' just paint order based upon the order the elements appear in the document text.
So you may find that using .append() your objects are in reverse order.
copy_list = []
reverse_order = True
for item in selection_list:
# TAG gives the element tag
if item.TAG == 'g':
for child in item.getchildren():
copy_list.append(child)
inkex.errormsg(copy_list)
inkex.errormsg('-----------------')
if reverse_order:
copy_list.reverse()
inkex.errormsg(copy_list)
for item in copy_list:
self.svg.append(item)
You can change reverse_order to False :)
-------------------------------------------------
In addition to .append() you can also use .addnext() and .addprevious() to append as siblings etc
-------------------------------------------------
This little piece of code can be run on any object to see the methods / attributes which are available
def get_attributes(self):
""" Returns a string containing all object attributes
- One attribute per line
"""
attribute_string = 'test'
for att in dir(self):
try:
attribute = (att, getattr(self, att))
attribute_string = attribute_string + str(attribute) + '\n'
except:
None
return attribute_string
inkex.errormsg(get_attributes(myobject))
Thanks for the detailed explanation! You really help me a lot!!! :) I was able to ungroup using
self.svg.append()
approach.But I found after I process the SVG by my program (1. delete layers, 2. convert group mode to Group, 3. ungroup layers, and 4. delete the empty layers.), the path
'd'
data changed although it looks the same visually.This is the original
'd'
<path
d="m 2226.9894,893.71266 3.1694,245.13604 245.136,-3.1694 -3.1694,-245.13603 z"
style="fill:none;stroke:#4c0000"
id="path159" />
This is the path processed by my program
<path
d="M 2226.989403,893.712661 L 2230.158786,1138.848680 L 2475.294814,1135.679296 L 2472.125421,890.543268 L 2226.989403,893.712661 z"
style="fill:none;stroke:#4C0000"
id="path159" />
The same path but its property 'd' converted from relative coordinates to absolute coordinates. I wonder if it is possible to retain the original data format because all the svg data processed by my Program 1 will be processed by my Program 2. Program 2 has already finished, but it only works for processing the original data format. When I run the Program 2 with the Program 1 processed svg data, I got error messages.
Thanks.
I'm not 100% sure why this is happening. Sometimes Inkscape will do this is there are transforms at the layer / group or object level.
You should however be able to use element.path.to_absolute() or element.path.to_relative() to go back and forth ?
I've only used this a couple of times:
deepcopy
is part of the pythoncopy
standard library ( is part of python as standard )You should be able to compare the paths you have in the original object to the copies in the root.
Inkscape will auto assign ids to the new paths.
Actually, I should point out probably better to use the built in .copy() or .duplicate() method if you are copying objects.
I do use deepcopy myself, but for a slightly different purpose.
https://inkscape.gitlab.io/extensions/documentation/source/inkex.elements._base.html?highlight=duplicate#inkex.elements._base.BaseElement.duplicate
Thanks, inklinea, I just found it a bit weird that the path data 'd' format covent between the relative and absolute back and forth. I found it is not because of ungroup process.
I might need to modify my Program 2 to make it more robust so that it can parse both data formats. I haven't used
path.to_absolute()
andpath.to_relative()
before, but I'll try, thanks!I'm not sure which process caused the conversion.
Hi, I was wondering why whey I run the code in this way below, I got the error message said
get_attributes()
is not defined. Thanks!I would suspect it is due to the get_attributes at the same level of indentation as the effect function.
I tend to keep my functions outside the main inkex.EffectExtension class
If you are using Windows or Linux I would recommend using a IDE which does highlighting.
I use IntelliJ Idea Community Edition.
Thanks, I am using VS Code on MAC M1.
When I put the functions inside of the main inkex.EffectExtension class, the highlights indicate the get_attributes is not defined. But when I put the functions outside of the main inkex.EffectExtesion class, it works. I was wonder is it better to put all the self-defined functions outside of the main class. Previously, I put all my functions inside of the main class.
It works in the way below.