Add "Ungroup all" function

Bug #171117 reported by Molumen on 2006-01-30
76
This bug affects 12 people
Affects Status Importance Assigned to Milestone
Inkscape
Wishlist
Matej

Bug Description

Hello,

I just realized while drawing a set of icons in
Inkscape that there is a funtion that could be (and I
think should be) added: "Ungroup all"

There is such a function in Corel Draw. It is actually
the same as Ungroup, but the main (and only) difference
is the following:

Let's say that you work with an objects group,
consisting of other groups that consist of other
groups... and so on for about let's say 10 times.

If you want to ungroup all the object's groups, you
will have to ungroup the 1st group, then ungroup the
secondary groups etc... It is time consuming (or i'd
better say "click consuming").

By adding an "Ungroup all" function, users will have
the possibility to ungroup all the selected groups with
one single click.

I hope that I explained the function clearly enough.

Molumen.

Horkana-users (horkana-users) wrote :

might be hard keeping the menus organised if we have a menu
for every function like this but sounds like a reasonably
good idea. bumping up priority

Molumen (molumen) wrote :

Thanks horkana, I really hope to see this function
implemented in the near future, as it is very usefull while
dealing with complex groupped svg files imported from other
applications (AI, Corel...).

Molumen

O--b (o--b) wrote :

It is also very usefull to export files to AI, groups seems
to mess up the outline thickness, so you have to remove all
groups to have a correct import of the SVG in AI

Molumen (molumen) wrote :

Hi! this funtion may be usefull when exporting SVG files to
Corel too: when there are no groupped objects corel behaves
normally, but when there are groupped objects left, corel
seems to see them as locked objects. Quite annoying
behaviour... Anyway, the "Ungroup All" function could be a
usefull thing.

Will we see it in the 0.45 release?

Thanks!

Bug Importer (bug-importer) wrote :

> Will we see it in the 0.45 release?

No one has volunteered to implement it yet so presumably the
answer is no.

However, if you were to gather together a shortlist of
enhancements such as this one which would be relatively easy
and suitable for implementing as extensions you might be
able to send a message to the mailing list and inspire some
interest.

I am always interested to know how other applications do
things. Do any other vector graphics applications offer an
"Ungroup All" option and if so how is it presented in the
user interface?

--
Alan <horkana>

Molumen (molumen) wrote :

>>>I am always interested to know how other applications
>>>do things. Do any other vector graphics applications
>>>offer an "Ungroup All" option and if so how is it
>>>presented in the user interface?

Yes, Corel Draw has that function since version 8 or even
version 7. Quite usefull function!

Ryan Lerch (ryanlerch) on 2007-11-28
Changed in inkscape:
importance: Medium → Wishlist
status: New → Confirmed
Aubanel (aubanel) on 2008-03-13
Changed in inkscape:
assignee: nobody → aubanel
jxp (jp-scientia) wrote :

Very usefull when ungrouping an imported PDF. It is a must!

mahfiaz (mahfiaz) wrote :

+1

That would be much better than hitting CTRL+SHIFT+G like a machine gun and wait :)
BTW, the ungrouping function is quite slow. Woulnd't be possible to add a visual hint of the process that is being performed?

mahfiaz (mahfiaz) wrote :

I would prefer fast ungroup instead of visual hints :)

Yes, of course. But improving performance is probably much more difficult than adding a progress bar, and meanwhile a visual hint would be acceptable.
Inkscape needs visual feedback for several time consuming processes that apparently hang the program (to the user eye).

Hi everybody,

I just wanted to stress a little more the need for the [ungroup all] function. Yesterday I imported a big PDF file generated by Adobe Indesign into inkscape and it took me almost 10 minutes to get rid of all the groups and begin to edit it.

Can anyone PLEASE tale a look at this feature request, I believe it is not too complicated to make and lots of designers would be VERY grateful.

Also, in reply to https://bugs.launchpad.net/inkscape/+bug/171117/comments/5 I would say that the best place for such a function would be in the context menu, right under the ungroup function.
I attached a screenshot of Corel Draw's context menu to this post.

tags: added: groups
removed: ungroup

I have been looking for writing such an extension because I have to edit ~50 pdf files created with AutoCAD and each of them have ~700 imbricated groups.

Using inkscape command line works but only for a couple of imbricated groups:
$ inkscape --verb=EditSelectAll --verb=SelectionUnGroup --verb=SelectionUnGroup --verb=FileSave --verb=FileClose file.svg

This will open file.svg and ungroup as many times as there are "--verb=SelectionUnGroup" options (here two ungrouping operations). So it is ok for tens of imbricated groups but not for hundreds. Moreover it is still quite slow especially because this command line does not work with the -z option (no GUI).

Then I searched how to write a Python extension (extending default 'inkex' extension). Unfortunately I have found neither documentation about any ungrouping function nor any use of such function in an existing extension. The closest code from what I want is: http://inkscape.modevia.com/doxygen/html/perspective_8py_source.php. This site also publish the Inkscape API. It should look like:

def recursive_ungrouping (self,nodes):
    for node in nodes:
        if node.tag == inkex.addNS('g','svg'):
            nodes_in_former_group = node.ungroup()
            self.recursive_ungrouping (nodes_in_former_group)

According to comments in http://ospublish.constantvzw.org/tools/inkscape-plugins-in-python, it is unfortunately not possible to access Inkscape built-in functions from Python plugins. Hence such functions would need to be implemented again in the Python plugin. This is not so trivial for ungrouping as the group may have transformation applied. I may implement it in Python though, if I find the ungrouping function in Inkscape source code.

Ungrouping is done with the function sp_selection_ungroup() from the selection-chemistry.cpp file. It calls sp_item_group_ungroup() from the sp-item-group.cpp file for styles and transforms.

A function very close to what we want is implemented in function sp_degroup_list() from selection-chemistry.cpp. This function does not ungroup but recursively lists all items in groups and subgroups in order to combine paths (used by sp_selected_path_combine() from path-chemistry.cpp).

The for loop of the sp_ungroup() function needs to be modified for the sp_ungroup_all() function in order to process "new_select" items that are groups. Modifications: name of the function, end of "for" loop, menu names at the end of the function. This gives (untested):

void sp_selection_ungroup_all(SPDesktop *desktop)
{
    if (desktop == NULL)
        return;

    Inkscape::Selection *selection = sp_desktop_selection(desktop);

    if (selection->isEmpty()) {
        desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select a <b>group</b> to ungroup."));
        return;
    }

    GSList *items = g_slist_copy((GSList *) selection->itemList());
    selection->clear();

    // Get a copy of current selection.
    GSList *new_select = NULL;
    bool ungrouped = false;
    for (GSList *i = items;
         i != NULL;
         i = i->next)
    {
        SPItem *group = (SPItem *) i->data;

        // when ungrouping cloned groups with their originals, some objects that were selected may no more exist due to unlinking
        if (!SP_IS_OBJECT(group)) {
            continue;
        }

        /* We do not allow ungrouping <svg> etc. (lauris) */
        if (strcmp(SP_OBJECT_REPR(group)->name(), "svg:g") && strcmp(SP_OBJECT_REPR(group)->name(), "svg:switch")) {
            // keep the non-group item in the new selection
            selection->add(group);
            continue;
        }

        GSList *children = NULL;
        /* This is not strictly required, but is nicer to rely on group ::destroy (lauris) */
        sp_item_group_ungroup(SP_GROUP(group), &children, false);
        ungrouped = true;

/* MAIN MODIFICATIONS FOR "UNGROUP ALL" */
        for (GSList *j = children; j != NULL; j = j->next)
        {
            // Add ungrouped items (children) to the list to be processed (items) if they are groups
            if(SP_IS_GROUP(j))
            {
                items = g_slist_append(items, j);
            }
            else
            // Add ungrouped items (children) to the new selection (new_select) if they are not groups
            {
                new_select = g_slist_append(new_select, j);
            }
        g_slist_free(children);
        }
/* END OF MAIN MODIFICATIONS */
    }

    if (new_select) { // Set new selection.
        selection->addList(new_select);
        g_slist_free(new_select);
    }
    if (!ungrouped) {
        desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No groups</b> to ungroup in the selection."));
    }

    g_slist_free(items);

    sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_UNGROUP_ALL,
                     _("Ungroup all"));
}

su_v (suv-lp) wrote :

Please that the code repository of Inkscape trunk has moved to Launchpad [1] after the release of Inkscape 0.47. Your code snippet seems to be based on an older version of 'src/selection-chemistry.cpp'.

current trunk version of 'src/selection-chemistry.cpp':
<http://bazaar.launchpad.net/~inkscape.dev/inkscape/trunk/annotate/head%3A/src/selection-chemistry.cpp>

[1] http://www.inkscape.org/bzr.php?lang=en
    https://code.launchpad.net/~inkscape.dev/inkscape/trunk

su_v (suv-lp) on 2010-06-27
tags: added: selection
removed: ui-selection-group-layer
DDJJ (jldai) wrote :

It looks like it is almost there. Hope to see it soon.

Matej (mcepl) wrote :

I have an extension for this at https://gitorious.org/inkscape-ungroup-deep/main

I have tested the extension from Gitorious. It works for moderate grouping but the extension failed on a 378 ko plain svg file with 900 nested groups. Here is the log:

Traceback (most recent call last):
File "ungroup_deep.py", line 53, in <module>
  effect.affect()
File "C:\Program Files (x86)\Inkscape\share\extensions\inkex.py", line 211, in affect
  self.parse()
File "C:\Program Files (x86)\Inkscape\share\extensions\inkex.py", line 139, in parse
  self.document = etree.parse(stream)
File "lxml.etree.pyx", line 2692, in lxml.etree.parse (src/lxml/lxml.etree.c:49594)
File "parser.pxi", line 1522, in lxml.etree._parseDocument (src/lxml/lxml.etree.c:71582)
File "parser.pxi", line 1552, in lxml.etree._parseFilelikeDocument (src/lxml/lxml.etree.c:71892)
File "parser.pxi", line 1435, in lxml.etree._parseDocFromFilelike (src/lxml/lxml.etree.c:70807)
File "parser.pxi", line 997, in lxml.etree._BaseParser._parseDocFromFilelike (src/lxml/lxml.etree.c:67948)
File "parser.pxi", line 539, in lxml.etree._ParserContext._handleParseResultDoc (src/lxml/lxml.etree.c:63824)
File "parser.pxi", line 625, in lxml.etree._handleParseResult (src/lxml/lxml.etree.c:64745)
File "parser.pxi", line 565, in lxml.etree._raiseParseError (src/lxml/lxml.etree.c:64088)
xml.etree.XMLSyntaxError: Excessive depth in document: 256 use XML_PARSE_HUGE option, line 1308, column 12

Matej (mcepl) wrote :

Right, and I won't do anything about it. Patches welcome, but I don't want to get this excessively complicated (note, that you've hit libxml limit, not problem of my code). What are the side-effects of XML_PARSE_HUGE option?

Selecting and ungrouping just some subgroups first could probably help.

Matej (mcepl) wrote :

Also note, that in inkscape 48.3 there was a bug (https://bugzilla.redhat.com/877681) which made Inkscape crash with this script. It has been fixed in 48.4 apparently.,

Also note, that my current repo is at http://luther.ceplovi.cz/git/inkscape-ungroup-deep.git/ not at gitorious.

Robin Battey (zanfur) wrote :

Here's a patch to inkex.py that adds the XML_PARSE_HUGE option to extensions, allowing the Deep Ungroup to work on deeply nested groups (and also the Color Replace, and actually every extension). I just put my modified inkex.py in my personal extensions folder, which works. Patch is against 0.48.4.

With this and Matej's "Deep Ungroup" extension (see previous comment), we have a working solution for the OP. Though, I admit, a cleaner solution would have been to extend the inkex.py to accept a "huge" option argument and allow the extensions to turn it on as needed, instead of just applying XML_PARSE_HUGE to all of them unilaterally.

Robin Battey (zanfur) wrote :

I found another shortcoming of Matej's otherwise-amazing deep ungroup extension: when you patch inkex to use the XML_PARSE_HUGE option, you can hit the Python function recursion depth limit in the "propagate_attribs" function. I've restructured it to be iterative with a stack instead of recursive. The "_ungroup" function is also recursive, and needs similar treatment.

There was also a nasty performance bug in the interaction between the propagate_attribs and _ungroup functions, causing propagate_attribs to be called applied multiple times per node: Because the _ungroup function propagates the attribs from the current object to the children, and the propagate_attribs function recursively propagates the attribs all the way down to the leaf nodes before returning, the objects at the bottom of the nested grouping have the propagate_attribs function called on them as many times as there is depth of nested grouping. If you have millions of elements that are thousands of levels of nesting deep, this is a HUGE slowdown.

I've incorporated the deep_utils.py and the deep_ungroup.py to do it all in one sweep through the hierarchy, touching each node only once, with no recursion, meaning it now runs in time O(N) and space O(log N) with a low coefficient. Before it was running in time O(N log N) and space O (log N) with a high coefficient, so this means a huge speedup with a small space decrease.

It is now trivial to add a "max depth of X" limit (add current depth as a member of the tuple in the queue and only ungroup if it's less than X), and possible to have a "leave the last Y levels" limit (perform the ungroup Y levels up from the current level instead of at the current level), though I've not implemented those features.

Matej (mcepl) wrote :

Thank you, Robin, that's a great job. Currently your change is in http://luther.ceplovi.cz/git/inkscape-ungroup-deep.git/commit/?h=iterative&id=00a19b7d3c31b16d0dbe1bd2a6483672c17e4db4 and I will (after a bit of clean up; I wanted to make the script PEP8-compliant anyway) merge it back to the master branch soon.

Jason Forester (hilobird) wrote :

You guys are brilliant.

I tried to get this going months ago, the repo was gone, no cache, despair.

I google again today, find the same thread, and damned if it's not only there, but updated, patched, extended too.

Fantastic work, the PARSE_HUGE attribute patch has it working on even some nested converted architectural PDFs i deal with.

THANKS!

Robin Battey (zanfur) wrote :

Looks like i accidentally uploaded a version that had been tabified. Here's the proper, no-tabs version. For the curious, each tab was supposed to be 8 spaces.

Robin Battey (zanfur) wrote :

And now, at matej's suggestion, a version that passes pep8 and PyFlakes.

Matej (mcepl) wrote :

Thank you for clean up, but I have now problems with its results. Please, open http://mcepl.fedorapeople.org/tmp/calendar_2013-04-28_2013-06-02.orig.pdf in Inkscape and run the script on it (without selecting anything ... all elements in the drawing should be ungrouped). View of the document shouldn't change.

Robin Battey (zanfur) wrote :

It performs exactly as your original one does, except faster (just tested). It also performs exactly how simply ungrouping manually would work -- I get the same results that I get when I select everything and simply ungroup manually (it takes 7 ungroups). Do you intend for your plugin to have different behavior than simply running the ungroup command a bunch of times?

The problem is that the clip path is not preserved when ungrouping, see here:

  http://www.inkscapeforum.com/viewtopic.php?f=5&t=5682

It could be changed recognize a clip and not ungroup that, or to transform the clipped object so that the clip is no longer necessary. I think the former is better. But, that's a functional change -- the cleanup didn't alter the functionality in any way that I can see.

I also see that deep ungroup doesn't play well with inkscape undo. Is that something easily fixable?

Robin Battey (zanfur) wrote :

Apparently it's a bit more complicated than that, even -- inkscape has a bug (https://bugs.launchpad.net/inkscape/+bug/567014) that prevents it from handling the clip-path attribute in the style. In order for this extension to work around it, it would have to track the clip-path nestings and create its own (nested) clip-path objects to assign the children in order to propagate clips without view changes.. That's pdefinitely ossible -- look at the "Layer Clip" extension -- but it's quite a bit more work. You'd have to keep the relevant clip-path objects in the queue and add sub-elements to them as you get new clip-paths.

On 13/04/13 08:04, Robin Battey wrote:
> Do you intend for your plugin to have different behavior than simply
> running the ungroup command a bunch of times?

Well, I don't know what else my old plugin did, but when opening the
mentioned PDF, it didn't change the view. Your one does. Given that
working on these Google Calendar exports is the main purpose of the
plugin for me, I cannot use your plugin however awesome it is (and I
don't dispute that).

Yes, I will take a look at https://bugs.launchpad.net/inkscape/+bug/567014

Robin Battey (zanfur) wrote :

Just to make sure we're seeing the same thing: The view change that I see is that the boxes around the date numbers in the upper right of each day area quadruple in size. Is that what you're talking about?

If so, what you're saying and what I'm seeing don't line up, because when I pull your plugin from git (commit a2c8c706306b05c7073ad0197b070f6eea7b1288, from Wed Jan 4 00:39:44 2012 +0100), and run it against http://mcepl.fedorapeople.org/tmp/calendar_2013-04-28_2013-06-02.orig.pdf, I get the exact same view changes as when I'm running my own version. Looking at the svg, and how your plugin (and mine!) works, it's not possible to avoid because the date number boxes are a group with a clip path that contains two paths (rounded rectangles for the box) and the text, and neither of our plugins track the clip-paths at all.

Perhaps google recently changed their generation of calendar exports? I believe you that it worked before, but neither your plugin nor mine, nor the standard ungroup function, will work on the PDF you linked without changing the view.

Robin Battey (zanfur) wrote :

I decided to go whole hog and I basically re-structured it from the ground up. I added the parameters I discussed in comment #23, allowing you to ungroup the whole document, leave levels of grouping at the top, leave levels of grouping at the bottom as measured from the top, and leave levels of grouping at the bottom as measured from the bottom. In your PDF, only the lowest level of grouping contains the odd clip-path behavior, so you can just set it to ungroup all but one level and the view doesn't change at all. You can also easily add any filter you'd like, possibly even an xpath expression, kind of like the color replace extension (see coloreffect.py in the inkscape distribution).

Your original plugin removed any and all inherited styles from the document, even for non-group containers (such as embedded svgs, anchors, and switches). I pulled the transform merging and style merging bits out into their own one-step-only helper functions, allowing me to propagate styles and transforms for only a single group instead of a group and *everything* underneath it. There's now a "_want_ungroup" function that is easy to modify if anyone adds filters of any kind, though I've already implemented the "count from top" and "count from bottom" varieties.

I also created a stub for propagating clip-paths to a group's children, but it's not implemented yet because it's not as simple as just copying it to the children: The child might already have a clip-path, requiring you to make a new hierarchical clip-path in the defs section of the svg; and any transform the child has apparently will apply the clip-path as well, meaning you'd need to apply the reverse of that transform to the clip-path itself in order to maintain the view.

This one should be pretty simple to follow: The _deep_ungroup is the recursing bit, tracking the depth from top and height from bottom in the stack. It does the ungrouping on the way up, instead of on the way down, because it has to hit bottom before it can know the height from the bottom. On the way back up, it queries the "_want_ungroup" function for every level, and if the function returns affirmative it calls the "_ungroup" helper function that does the heavy lifting to actually flatten the group.

Matej (mcepl) wrote :

That is not what I see. When I open the PDF (without doing anything with it) I see http://mcepl.fedorapeople.org/tmp/before.png. After running my script I see http://mcepl.fedorapeople.org/tmp/after-recursive.png (i.e., there is no visible change). After running your script I see http://mcepl.fedorapeople.org/tmp/after-iterative.png (a lot of text is lost).

Matej (mcepl) wrote :

Interesting, the last version from comment 33 seems to work correctly ... http://mcepl.fedorapeople.org/tmp/after-iterative-02.png Well, there are some changes, but that's probably really the result of clipping.

Robin Battey (zanfur) wrote :

Your pictures contradict some of your statements. If you look at http://mcepl.fedorapeople.org/tmp/after-recursive.png (after your original plugin), and at the http://mcepl.fedorapeople.org/tmp/after-iterative-02.png (after my latest iterative version), you'll see they're the same as each other, and both different than http://mcepl.fedorapeople.org/tmp/before.png (the original) . Your plugin also makes changes due to the clipping issue -- there *is* a visible change.

There was a point where I had a bug (not propagating the transform correctly) that caused the missing text that you see, but I didn't think I'd uploaded that version. I'd go check, but it's fixed in the latest version anyway, so I'll take your word (and evidence!) for it that it was broken. Sorry about that. In any case, our plugins now give the same result, though the iterative version works where the svg depth is greater than 1024 and the recursive one doesn't, and the iterative one has a few more options now. It also runs vastly faster and avoids doing a bunch of changes unrelated to ungrouping that came as part of using the global inheritance-removal script.

Matej (mcepl) wrote :

I think you are right ... iterative branch merged to the master. Thank you!

Robin Battey (zanfur) wrote :

I couldn't leave well enough alone, so I added the clip-path transform tracking. Now it ungroups without any visual change whatsoever. It's a little slower when there are many nested clip-paths because I have it set up an inverted tree of nested clip-path references, but it's still pretty fast. The big issue is that when you apply a clip-path to an element with a transform, the transform applies to the clip-path as well...so when you hit that scenario you need to make a copy of the clip-path you want to apply, calculate the inverse transform to offset the transform of the target node, apply that inverse to your copy of the clip-path, intersect it with any current clip-path of the target node, and then apply your transformed and intersected clip-path to the target node. To make matters worse, you can't just set the transform on the clipPath element itself, but you have to apply it to all the children. :-/ Anyway, it's done. 4 hours for what ends up being about 35 lines of additional code.

Something similar probably needs to be done to track masks as well, but I'll leave that to someone else -- it's likely pretty close to the exact same code .

Matej (mcepl) wrote :

Unfortunately, inkscape started to crash on me (unrelated to this plugin, I think). Will merge when I can test.

Robin Battey (zanfur) wrote :

Any further progress on this?

I'd find such a function quite useful. Been having problem when importing from Inkscape to AI due to nested groups.

Matej (mcepl) wrote :

@#40 I thought I have pushed it to my repo a long time ago. But apparently not. It is there now.

http://luther.ceplovi.cz/git/inkscape-ungroup-deep.git/

@#41: what's wrong with my plugin?

jazzynico (jazzynico) wrote :

Commit a890d5c325bb4a499d65254c80b6bc9ed2779856 from http://luther.ceplovi.cz/git/inkscape-ungroup-deep.git/ (2013-04-18 06:49:15) tested successfully on Windows XP, Inkscape trunk revision 12845, with the test file provided in the git repo.
More tests needed with various complexity.

Changed in inkscape:
assignee: Aubanel (aubanel) → Matej (mcepl)
status: Confirmed → In Progress
Dick Andersson (8-dick) wrote :

I can not get the plugin to work! ...but I have a solution. Drag and drop the svg-document into an empty Libre Office Draw document. Mark, right click and select "break down" (translated from the menu choice in the Swedish version of LibreOffice, and it's not ungroup). Select all, copy and go back to inkscape, paste and ungroup.

Matej (mcepl) wrote :

@8-dick Could you please elaborate on "can not get the plugin to work!", please? What's wrong with http://luther.ceplovi.cz/git/inkscape-ungroup-deep.git/tag/?id=0.2

Matt Sim (mr-matt-sim) wrote :

## work-around
I was searching for this function, and i agree it would be useful. But for now i've discovered a work-around

1. Find (Ctrl-F)
2. uncheck All types
3. uncheck Groups
     3.5 add any more options as needed: Search in selection/layer, hidden, etc
4. Find
5. wait for it...
6. Cut (Ctrl-C)
7. Paste in place (Ctrl-Alt-V)

Huzzah!

jazzynico (jazzynico) on 2016-05-23
Changed in inkscape:
milestone: none → 0.92
Matej (mcepl) wrote :

Just another move of the repository, it is https://gitlab.com/mcepl/inkscape_ungroup_all now.

jazzynico (jazzynico) wrote :

New extension available in the trunk (and thus in the upcoming 0.92) as of rev. 14926.

Thanks Matej!

Changed in inkscape:
status: In Progress → Fix Committed
Bryce Harrington (bryce) on 2017-01-10
Changed in inkscape:
status: Fix Committed → Fix Released
Andrew Scott (tfphoenixx) wrote :

Matej you are the people's champ! This extension is extremely helpful to me thank you!

To post a comment you must log in.
This report contains Public information  Edit
Everyone can see this information.

Duplicates of this bug

Other bug subscribers