Inkscape.org
Creating New Extensions Ungroup layers or move paths from other layers to one layer
  1. #1
    czkPanda czkPanda @czkPanda
    *

    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)

     

  2. #2
    inklinea inklinea @inklinea⛰️

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

  3. #3
    czkPanda czkPanda @czkPanda

    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. 

  4. #4
    inklinea inklinea @inklinea⛰️

    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.

    Inkscape will auto assign ids to the new paths.

  5. #5
    inklinea inklinea @inklinea⛰️

    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

  6. #6
    czkPanda czkPanda @czkPanda

    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!

     

  7. #7
    czkPanda czkPanda @czkPanda

    I'm not sure which process caused the conversion. 

     

  8. #8
    czkPanda czkPanda @czkPanda
    *

    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()

     

  9. #9
    inklinea inklinea @inklinea⛰️

    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.

  10. #10
    czkPanda czkPanda @czkPanda

    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.  

  11. #11
    czkPanda czkPanda @czkPanda

    It works in the way below.

Inkscape Inkscape.org Inkscape Forum Creating New Extensions Ungroup layers or move paths from other layers to one layer