| Server IP : 93.86.61.54 / Your IP : 216.73.216.206 Web Server : Apache/2.4.62 (Ubuntu) System : Linux rasin.ddns.net 6.8.0-124-generic #124~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 21:05:19 UTC x86_64 User : www-data ( 33) PHP Version : 8.4.22 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /usr/share/inkscape/extensions/ |
Upload File : |
#!/usr/bin/env python
# coding=utf-8
"""
see #inkscape on Freenode and
https://github.com/nikitakit/svg2sif/blob/master/synfig_prepare.py#L370
for an example how to do the transform of parent to children.
"""
import inkex
from inkex import (
Group, Anchor, Switch, NamedView, Defs, Metadata, ForeignObject,
ClipPath, Use, SvgDocumentElement,
)
class UngroupDeep(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("--startdepth", type=int, default=0,
help="starting depth for ungrouping")
pars.add_argument("--maxdepth", type=int, default=65535,
help="maximum ungrouping depth")
pars.add_argument("--keepdepth", type=int, default=0,
help="levels of ungrouping to leave untouched")
@staticmethod
def _merge_style(node, style):
"""Propagate style and transform to remove inheritance
Originally from
https://github.com/nikitakit/svg2sif/blob/master/synfig_prepare.py#L370
"""
# Compose the style attribs
this_style = node.style
remaining_style = {} # Style attributes that are not propagated
# Filters should remain on the top ancestor
non_propagated = ["filter"]
for key in non_propagated:
if key in this_style.keys():
remaining_style[key] = this_style[key]
del this_style[key]
# Create a copy of the parent style, and merge this style into it
parent_style_copy = style.copy()
parent_style_copy.update(this_style)
this_style = parent_style_copy
# Merge in any attributes outside of the style
style_attribs = ["fill", "stroke"]
for attrib in style_attribs:
if node.get(attrib):
this_style[attrib] = node.get(attrib)
del node.attrib[attrib]
if isinstance(node, (SvgDocumentElement, Anchor, Group, Switch)):
# Leave only non-propagating style attributes
if not remaining_style:
if "style" in node.keys():
del node.attrib["style"]
else:
node.style = remaining_style
else:
# This element is not a container
# Merge remaining_style into this_style
this_style.update(remaining_style)
# Set the element's style attribs
node.style = this_style
def _merge_clippath(self, node, clippathurl):
if clippathurl and clippathurl != "none":
node_transform = node.transform
if node_transform:
# Clip-paths on nodes with a transform have the transform
# applied to the clipPath as well, which we don't want. So, we
# create new clipPath element with references to all existing
# clippath subelements, but with the inverse transform applied
new_clippath = self.svg.defs.add(ClipPath(clipPathUnits='userSpaceOnUse'))
new_clippath.set_random_id('clipPath')
clippath = self.svg.getElementById(clippathurl[5:-1])
for child in clippath.iterchildren():
new_clippath.add(Use.new(child, 0, 0))
# Set the clippathurl to be the one with the inverse transform
clippathurl = "url(#" + new_clippath.get("id") + ")"
# Reference the parent clip-path to keep clipping intersection
# Find end of clip-path chain and add reference there
node_clippathurl = node.get("clip-path")
while node_clippathurl:
node = self.svg.getElementById(node_clippathurl[5:-1])
node_clippathurl = node.get("clip-path")
node.set("clip-path", clippathurl)
# Flatten a group into same z-order as parent, propagating attribs
def _ungroup(self, node):
node_parent = node.getparent()
node_index = list(node_parent).index(node)
node_style = node.style
node_transform = node.transform
node_clippathurl = node.get('clip-path')
for child in reversed(list(node)):
child.transform = node_transform * child.transform
if node.get("style") is not None:
self._merge_style(child, node_style)
self._merge_clippath(child, node_clippathurl)
node_parent.insert(node_index, child)
node_parent.remove(node)
# Put all ungrouping restrictions here
def _want_ungroup(self, node, depth, height):
if (isinstance(node, Group) and
node.getparent() is not None and
height > self.options.keepdepth and
self.options.startdepth <= depth <=
self.options.maxdepth):
return True
return False
def _deep_ungroup(self, node):
# using iteration instead of recursion to avoid hitting Python
# max recursion depth limits, which is a problem in converted PDFs
# Seed the queue (stack) with initial node
q = [{'node': node,
'depth': 0,
'prev': {'height': None},
'height': None}]
while q:
current = q[-1]
node = current['node']
depth = current['depth']
height = current['height']
# Recursion path
if height is None:
# Don't enter non-graphical portions of the document
if isinstance(node, (NamedView, Defs, Metadata, ForeignObject)):
q.pop()
# Base case: Leaf node
if not isinstance(node, Group) or not list(node):
current['height'] = 0
# Recursive case: Group element with children
else:
depth += 1
for child in node.iterchildren():
q.append({'node': child, 'prev': current,
'depth': depth, 'height': None})
# Return path
else:
# Ungroup if desired
if self._want_ungroup(node, depth, height):
self._ungroup(node)
# Propagate (max) height up the call chain
height += 1
previous = current['prev']
prev_height = previous['height']
if prev_height is None or prev_height < height:
previous['height'] = height
# Only process each node once
q.pop()
def effect(self):
if self.svg.selected:
for node in self.svg.selected.values():
self._deep_ungroup(node)
else:
for node in self.document.getroot():
self._deep_ungroup(node)
if __name__ == '__main__':
UngroupDeep().run()