Collection of mostly command line tools / PHP scripts. Somewhat out of date.

⌈⌋ branch:  scripts + snippets


Check-in [58335bd8d7]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:prepare potential all-merge mode
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 58335bd8d7b2a0d39e3f5d83fdef7a96828cd1bb
User & Date: mario 2023-05-23 10:10:47
Context
2023-11-10
23:35
fix `attrib` method name, and translate float detection Leaf check-in: 519aaabdbf user: mario tags: trunk
2023-05-23
10:10
prepare potential all-merge mode check-in: 58335bd8d7 user: mario tags: trunk
2022-10-31
09:39
changed spline pacing mode: one less iteration to avert last section stuck at 1.0, reset .frame.args.delay just in case (repeat animations, e.g. fg/bg frames) check-in: 483352ece2 user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to inkscape/export_gif.py.

1
2
3
4
5
6
7
8
9
10
11
12

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/env python
# encoding: utf-8
# api: inkscape
##type: effect
# category: export
# title: GIF slideshow
# description: Export and combine layers as animation via ImageMagick/Pillow
# id: org.include-once.inkscape.export-gif
# license: MITL
# version: 1.1
# state: stable
# depends: bin:inkscape (>= 1.1), bin:convert, python (>= 3.6), python:svgelements

# pylint: disable=line-too-long, missing-module-docstring, bad-whitespace
# config:
#    { name: file, type: file, mode: file, value: "~/anim.gif", description: "Target GIF filename", help: "Output filename should end in .gif for actual results. Alternatively can be `APNG:dir/filename.apng` with ImageMagick 7." }
#    { name: mode, type: select, select: "PNG→ImageMagick (better quality)|SVG→ImageMagick (simple drawings)|PNG→Pillow (builtin - a bit faster)|JavaScript→SVG (embed anim code)", value: PNG+ImageMagick, description: Conversion mode, help: "There's different backends to assemble the GIF. Pillow also works on Windows without convert.exe installed. The JavaScript mode embeds some code, does not actually generate a GIF output file." }
#    { name: delay, type: float, value: 0.35, min: 0.01, max: 20, precision: 2, description: "Delay between slides (seconds)", "Can be overriden on a per-slide basis with [--delay=2.5]. Note that convert(1) usually uses »ticks« (*10ms), but this export_gif option is really in seconds." }
#    { name: loop, type: int, value: 0, description: "Loop limit (0 for endless)" }
#    [ hidden: 1, disabled: save_as, type: str, value: "0", description: used in saveas_gif.inx, note: "have to use `str` here, cause .inx `false` is not recognized for hidden attributes" ]
#    { xml: <image width="505" height="50">export_gif.svg</image> }
#    { name: notebook, type: notebook, value: system, description: "Additional flags and help" }
#    { category: ImageMagick, name: fuzz, type: select, select: "0%|5%|10%|20%|30%|50%", value: "10%", description: "Fuzzing/dither", help: "Detect similar colors for optimization" }
#    { category: ImageMagick, name: layers, type: select, select: "optimize|coalesce|compare-any|compare-clear|compare-overlay|composite|dispose|flatten|merge|mosaic|optimize-frame|optimize-plus|optimize-transparency|remove-dups|remove-zero|trim-bounds", value: "optimize", description: "Accumulation/combination of -layers", help: "Can influence optimization or layer combination. Specifically coalesce/merge are pre-interpreted by export_gif to produce accumulating slides." }
#    { category: ImageMagick, name: extra, type: select, select: "-quiet|-optimize|-size 640x360|-alpha background|-auto-gamma|-auto-level|-coalesce|-colors 64|-dither FloydSteinberg|-limit disk 1MB|-reverse|-monochrome|-transparent white", value: "", description: "Extra args", help: "Some default convert -args." }
#    { category: ImageMagick, name: extra2, type: str, value: "", description: "Custom args", help: "Specify additional convert(1) options." }
#    { category: ImageMagick, name: background, type: color, appearance: colorbutton, value: "0", description: "Background color for transparent layers", help: "Only works for SVG→ImageMagick option." }
#    { category: System, name: preview, type: bool, value: 0, description: "Preview result file", help: "Should bring up default image viewer on resulting GIF (via xdg-open, or start… on Windows)" }
#    { category: System, name: keep_tmp, type: bool, value: 0, description: "Keep temporary files", help: "Will retain the frame PNGs in the fixed directory /tmp/inkscape.export_gif/" }
#    { category: System, name: reload_svg, type: bool, value: 0, description: "Reload SVG in Inkscape", help: "Averts the warning popup of lacking result data. But is quite redundant for this tool. And should only be enabled if it's becoming too obnoxious." }
#    { category: System, name: export_background, type: bool, value: 0, description: "Force background application in PNG export", help: "Uses --export-background for generating PNG slides prior assembly. (See ImageMagick for color tab.)" }

#    { category: Animation, name: subframes, type: int, value: 5, description: "Subframes per ❮animation❯ slide", help: "Can be overridden per [animate=25] or [steps=25] in layer label." }
#    { category: Animation, name: a_rotate, type: bool, value: 1, description: "Use simpler rotate() handler", help: "Actually works better than tweening, and allows for >90° rotations without collapsing the matrix. OTOH the inkex method might handle positioning better." }
#    { category: Animation, name: all_anim, type: bool, value: 0, description: "Engage whenever SVG animate instructions are present", help: "Makes [animate] tags redundant, at the expense of longer processing times; and repeat runs for background layers." }
#    { category: Animation, name: all_pace, type: bool, value: 0, description: "Honor timing information for all slides", help: "Honor begin=, dur=, and some calcMode= settings, for pacing or delayed timing. Otherwise individual layers can be marked with [pace] or [smooth] in addition to [animate]. Else animations run/stretch across the alloted delay time for a slide." }
#    { category: Help, label: "Layer labels (Ctrl+Shift+L, double click) can specify additional options:" }
#    { category: Help, label: " 🞂 [fixed] for very permanent foreground layer" }
#    { category: Help, label: " 🞂 [background] for sticky background images" }
#    { category: Help, label: " 🞂 [merge] enjoins partial layers; and [exclude] skips them" }
#    { category: Help, label: " 🞂 [animate=10] generates ❮animate*❯ subframes, flag: [pace] timing" }
#    { category: Help, label: " 🞂 [--delay=2.5] resets ImageMagick option (delay also for Pillow)",
#    { category: Help, label: $help, appearance: url }
# inx-export: .gif, image/gif, GIF slideshow (*.gif), Graphics Interchange Format 98a
# architecture: all
# pack: export_gif.py, *.inx, pmd2inks, animate_yo.py, svgelements.py, export_gif.svg, LICENSE=/usr/share/doc/inkscape-export-gif/copyright
# depends: python:inkex, bin:inkscape >= 1.1
# suggests: python:svgelements >= 1.8
# permissive: 0.5
# format: off
# author: mario#include-once:org
# url: https://inkscape.org/~culturaljuice/★export_gif
# help: https://fossil.include-once.org/scripts/wiki/inkscape
# orig: https://github.com/jespino/inkscape-export-layers
#
# Generates a GIF slideshow from image layers ☰  (Shift+Ctrl+L). The menu
# option Extensions➜Export➔GIF-slideshow… iterates over layers to generate
# animation slides. They get merged from bottom to top. Depending on shape
# overlap or layer transparency, there's different usage modes:
#
#  🞂 With a solid background in each layer, there's no accumulation to
#    take care of.
#  🞂 Alternatively elect layers` can be labeled as [background] to stick,
#    or [fixed] as permanent foreground, or [merge] for down-grouping.
#  🞂 If the -layers option is composite/coalesce/merge then all lower
#    layers remain visible until the current frame.
#  🞂 Any [animate] layers get interpolated as is, layer transparency
#    preserved.
#
# Requires ImageMagick installed; fit for standard Linux setups. But might











|
>


















>














<
<








|





|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/env python
# encoding: utf-8
# api: inkscape
##type: effect
# category: export
# title: GIF slideshow
# description: Export and combine layers as animation via ImageMagick/Pillow
# id: org.include-once.inkscape.export-gif
# license: MITL
# version: 1.1
# state: stable
# depends: bin:inkscape (>= 1.1), bin:convert, python (>= 3.6)
# suggests: python:svgelements >= 1.8
# pylint: disable=line-too-long, missing-module-docstring, bad-whitespace
# config:
#    { name: file, type: file, mode: file, value: "~/anim.gif", description: "Target GIF filename", help: "Output filename should end in .gif for actual results. Alternatively can be `APNG:dir/filename.apng` with ImageMagick 7." }
#    { name: mode, type: select, select: "PNG→ImageMagick (better quality)|SVG→ImageMagick (simple drawings)|PNG→Pillow (builtin - a bit faster)|JavaScript→SVG (embed anim code)", value: PNG+ImageMagick, description: Conversion mode, help: "There's different backends to assemble the GIF. Pillow also works on Windows without convert.exe installed. The JavaScript mode embeds some code, does not actually generate a GIF output file." }
#    { name: delay, type: float, value: 0.35, min: 0.01, max: 20, precision: 2, description: "Delay between slides (seconds)", "Can be overriden on a per-slide basis with [--delay=2.5]. Note that convert(1) usually uses »ticks« (*10ms), but this export_gif option is really in seconds." }
#    { name: loop, type: int, value: 0, description: "Loop limit (0 for endless)" }
#    [ hidden: 1, disabled: save_as, type: str, value: "0", description: used in saveas_gif.inx, note: "have to use `str` here, cause .inx `false` is not recognized for hidden attributes" ]
#    { xml: <image width="505" height="50">export_gif.svg</image> }
#    { name: notebook, type: notebook, value: system, description: "Additional flags and help" }
#    { category: ImageMagick, name: fuzz, type: select, select: "0%|5%|10%|20%|30%|50%", value: "10%", description: "Fuzzing/dither", help: "Detect similar colors for optimization" }
#    { category: ImageMagick, name: layers, type: select, select: "optimize|coalesce|compare-any|compare-clear|compare-overlay|composite|dispose|flatten|merge|mosaic|optimize-frame|optimize-plus|optimize-transparency|remove-dups|remove-zero|trim-bounds", value: "optimize", description: "Accumulation/combination of -layers", help: "Can influence optimization or layer combination. Specifically coalesce/merge are pre-interpreted by export_gif to produce accumulating slides." }
#    { category: ImageMagick, name: extra, type: select, select: "-quiet|-optimize|-size 640x360|-alpha background|-auto-gamma|-auto-level|-coalesce|-colors 64|-dither FloydSteinberg|-limit disk 1MB|-reverse|-monochrome|-transparent white", value: "", description: "Extra args", help: "Some default convert -args." }
#    { category: ImageMagick, name: extra2, type: str, value: "", description: "Custom args", help: "Specify additional convert(1) options." }
#    { category: ImageMagick, name: background, type: color, appearance: colorbutton, value: "0", description: "Background color for transparent layers", help: "Only works for SVG→ImageMagick option." }
#    { category: System, name: preview, type: bool, value: 0, description: "Preview result file", help: "Should bring up default image viewer on resulting GIF (via xdg-open, or start… on Windows)" }
#    { category: System, name: keep_tmp, type: bool, value: 0, description: "Keep temporary files", help: "Will retain the frame PNGs in the fixed directory /tmp/inkscape.export_gif/" }
#    { category: System, name: reload_svg, type: bool, value: 0, description: "Reload SVG in Inkscape", help: "Averts the warning popup of lacking result data. But is quite redundant for this tool. And should only be enabled if it's becoming too obnoxious." }
#    { category: System, name: export_background, type: bool, value: 0, description: "Force background application in PNG export", help: "Uses --export-background for generating PNG slides prior assembly. (See ImageMagick for color tab.)" }
#    { category: System, name: default_merge, type: bool, value: 0, description: "Default all layers to [merge] - not implemented yet", help: "Requires to annotate actual slides with [background] or [export], because it defaults everything else to down-grouping [merge]" }
#    { category: Animation, name: subframes, type: int, value: 5, description: "Subframes per ❮animation❯ slide", help: "Can be overridden per [animate=25] or [steps=25] in layer label." }
#    { category: Animation, name: a_rotate, type: bool, value: 1, description: "Use simpler rotate() handler", help: "Actually works better than tweening, and allows for >90° rotations without collapsing the matrix. OTOH the inkex method might handle positioning better." }
#    { category: Animation, name: all_anim, type: bool, value: 0, description: "Engage whenever SVG animate instructions are present", help: "Makes [animate] tags redundant, at the expense of longer processing times; and repeat runs for background layers." }
#    { category: Animation, name: all_pace, type: bool, value: 0, description: "Honor timing information for all slides", help: "Honor begin=, dur=, and some calcMode= settings, for pacing or delayed timing. Otherwise individual layers can be marked with [pace] or [smooth] in addition to [animate]. Else animations run/stretch across the alloted delay time for a slide." }
#    { category: Help, label: "Layer labels (Ctrl+Shift+L, double click) can specify additional options:" }
#    { category: Help, label: " 🞂 [fixed] for very permanent foreground layer" }
#    { category: Help, label: " 🞂 [background] for sticky background images" }
#    { category: Help, label: " 🞂 [merge] enjoins partial layers; and [exclude] skips them" }
#    { category: Help, label: " 🞂 [animate=10] generates ❮animate*❯ subframes, flag: [pace] timing" }
#    { category: Help, label: " 🞂 [--delay=2.5] resets ImageMagick option (delay also for Pillow)",
#    { category: Help, label: $help, appearance: url }
# inx-export: .gif, image/gif, GIF slideshow (*.gif), Graphics Interchange Format 98a
# architecture: all
# pack: export_gif.py, *.inx, pmd2inks, animate_yo.py, svgelements.py, export_gif.svg, LICENSE=/usr/share/doc/inkscape-export-gif/copyright


# permissive: 0.5
# format: off
# author: mario#include-once:org
# url: https://inkscape.org/~culturaljuice/★export_gif
# help: https://fossil.include-once.org/scripts/wiki/inkscape
# orig: https://github.com/jespino/inkscape-export-layers
#
# Generates a GIF slideshow from image layers ☰  (Shift+Ctrl+L). The menu
# entry Extensions➜Export➔GIF-slideshow… iterates over layers to generate
# animation slides. They get merged from bottom to top. Depending on shape
# overlap or layer transparency, there's different usage modes:
#
#  🞂 With a solid background in each layer, there's no accumulation to
#    take care of.
#  🞂 Alternatively elect layers can be labeled as [background] to stick,
#    or [fixed] as permanent foreground, or [merge] for down-grouping.
#  🞂 If the -layers option is composite/coalesce/merge then all lower
#    layers remain visible until the current frame.
#  🞂 Any [animate] layers get interpolated as is, layer transparency
#    preserved.
#
# Requires ImageMagick installed; fit for standard Linux setups. But might
93
94
95
96
97
98
99


100
101
102
103
104
105
106
#    labels for more refined combinations.
#  🞂 Can't run two instances in parallel, due to fixed /tmp/inkscape… dir
#  🞂 It's also available as File > Save As... option (fewer options).
#  🞂 Tested on Inkscape 1.2 (2022-05-xx) and 1.3-dev (2022-09-xx)
#
# Might require some experimentation, and heavily utilizing the preview
# option. Potential error popups become informative at the very end.


#
# Code is derived from Xavi's "Export Layers Redux For Inkscape 1.1+".
# <https://inkscape.org/~VagabondArcade/%E2%98%85export-layers-redux-for-inkscape-11>
#
# ZIP contains the `pmd2inks` command-line tool to ease extending the
# options dialog (using the <https://pypi.org/project/pluginconf/> format).








>
>







93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#    labels for more refined combinations.
#  🞂 Can't run two instances in parallel, due to fixed /tmp/inkscape… dir
#  🞂 It's also available as File > Save As... option (fewer options).
#  🞂 Tested on Inkscape 1.2 (2022-05-xx) and 1.3-dev (2022-09-xx)
#
# Might require some experimentation, and heavily utilizing the preview
# option. Potential error popups become informative at the very end.
# The save as dialog ".gif" option provides fewer options, but effectively
# same behaviours for layer flags.
#
# Code is derived from Xavi's "Export Layers Redux For Inkscape 1.1+".
# <https://inkscape.org/~VagabondArcade/%E2%98%85export-layers-redux-for-inkscape-11>
#
# ZIP contains the `pmd2inks` command-line tool to ease extending the
# options dialog (using the <https://pypi.org/project/pluginconf/> format).

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import math
import inkex
from inkex.utils import strargs
from inkex.tween import StyleInterpolator, TransformInterpolator
from inkex.transforms import Transform


class GIFExport(inkex.EffectExtension):#, inkex.OutputExtension):#, inkex.RasterOutputExtension
    """ doubles as effect and save-as extension """
    multi_inx = True

    def __init__(self):
        """ init the effect library """
        self.tempdir = None
        self.index = 0 # current slide







|







120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import math
import inkex
from inkex.utils import strargs
from inkex.tween import StyleInterpolator, TransformInterpolator
from inkex.transforms import Transform


class GIFExport(inkex.EffectExtension):#, inkex.OutputExtension (for export pane):#, inkex.RasterOutputExtension (only works for static PNG conversion)
    """ doubles as effect and save-as extension """
    multi_inx = True

    def __init__(self):
        """ init the effect library """
        self.tempdir = None
        self.index = 0 # current slide
146
147
148
149
150
151
152

153
154
155
156
157
158
159
        pars.add_argument("--extra", type=str, dest="extra", default="", help="Extra args")
        pars.add_argument("--extra2", type=str, dest="extra2", default="", help="Custom args")
        pars.add_argument("--background", type=inkex.Color, dest="background", default="0", help="Background color for transparent layers")
        pars.add_argument("--preview", type=inkex.Boolean, dest="preview", default=False, help="Preview result file")
        pars.add_argument("--keep_tmp", type=inkex.Boolean, dest="keep_tmp", default=False, help="Keep temporary files")
        pars.add_argument("--reload_svg", type=inkex.Boolean, dest="reload_svg", default=False, help="Reload SVG in Inkscape")
        pars.add_argument("--export_background", type=inkex.Boolean, dest="export_background", default=False, help="Force background application in PNG export")

        pars.add_argument("--subframes", type=int, dest="subframes", default=5, help="Subframes per ❮animation❯ slide")
        pars.add_argument("--a_rotate", type=inkex.Boolean, dest="a_rotate", default=True, help="Use simpler rotate() handler")
        pars.add_argument("--all_anim", type=inkex.Boolean, dest="all_anim", default=False, help="Engage whenever SVG animate instructions are present")
        pars.add_argument("--all_pace", type=inkex.Boolean, dest="all_pace", default=False, help="Honor timing information for all slides")
        # only used in saveas_gif.inx (but not export_gif.inx, else hidden argument would be populated from preferences.xml history)
        pars.add_argument("--save_as", type=bool, dest="save_as", default=False, help="used in saveas_gif.inx")








>







148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
        pars.add_argument("--extra", type=str, dest="extra", default="", help="Extra args")
        pars.add_argument("--extra2", type=str, dest="extra2", default="", help="Custom args")
        pars.add_argument("--background", type=inkex.Color, dest="background", default="0", help="Background color for transparent layers")
        pars.add_argument("--preview", type=inkex.Boolean, dest="preview", default=False, help="Preview result file")
        pars.add_argument("--keep_tmp", type=inkex.Boolean, dest="keep_tmp", default=False, help="Keep temporary files")
        pars.add_argument("--reload_svg", type=inkex.Boolean, dest="reload_svg", default=False, help="Reload SVG in Inkscape")
        pars.add_argument("--export_background", type=inkex.Boolean, dest="export_background", default=False, help="Force background application in PNG export")
        pars.add_argument("--default_merge", type=inkex.Boolean, dest="default_merge", default=False, help="Default all layers to [merge] - not implemented yet")
        pars.add_argument("--subframes", type=int, dest="subframes", default=5, help="Subframes per ❮animation❯ slide")
        pars.add_argument("--a_rotate", type=inkex.Boolean, dest="a_rotate", default=True, help="Use simpler rotate() handler")
        pars.add_argument("--all_anim", type=inkex.Boolean, dest="all_anim", default=False, help="Engage whenever SVG animate instructions are present")
        pars.add_argument("--all_pace", type=inkex.Boolean, dest="all_pace", default=False, help="Honor timing information for all slides")
        # only used in saveas_gif.inx (but not export_gif.inx, else hidden argument would be populated from preferences.xml history)
        pars.add_argument("--save_as", type=bool, dest="save_as", default=False, help="used in saveas_gif.inx")

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
        up_until_mode = re.search("merge|compo|coal|plus", self.options.layers)
        preconvert_png = re.search("PNG", self.options.mode, re.I)
        img_ext = "png" if preconvert_png else "svg"

        # traverse layers, iteratively slicing for individual .svg/.png frames
        for self.index, layer in enumerate(layers):

            if layer.no_frame: # fixed, exclude
                continue

            if layer.is_sticky or up_until_mode:
                fixed_layers.append(layer.id)
            merge_layers = [
                follow.id for follow in layers[self.index+1:] if follow.do_merge
            ]

            if layer.animate or self.is_animatable(layer):
                # produces multiple subframes
                anim = AnimationSteps(
                    parent = self, layer = layer,
                    svg = self.export_layers(dest="", show=[layer.id] + fixed_layers + merge_layers)
                )
                temp_files_args.extend(list(anim.export()))
            else:
                # current frame into .svg/.png







|









|







190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
        up_until_mode = re.search("merge|compo|coal|plus", self.options.layers)
        preconvert_png = re.search("PNG", self.options.mode, re.I)
        img_ext = "png" if preconvert_png else "svg"

        # traverse layers, iteratively slicing for individual .svg/.png frames
        for self.index, layer in enumerate(layers):

            if layer.no_frame: # [fixed], [exclude]
                continue

            if layer.is_sticky or up_until_mode:
                fixed_layers.append(layer.id)
            merge_layers = [
                follow.id for follow in layers[self.index+1:] if follow.do_merge
            ]

            if layer.animate or self.is_animatable(layer):
                # produces multiple subframe .pngs
                anim = AnimationSteps(
                    parent = self, layer = layer,
                    svg = self.export_layers(dest="", show=[layer.id] + fixed_layers + merge_layers)
                )
                temp_files_args.extend(list(anim.export()))
            else:
                # current frame into .svg/.png
350
351
352
353
354
355
356





357
358
359
360
361
362
363

    def get_layers(self):
        """ Per default all layers are exported, label tags [bg], [exclude], [merge] yield special treatment """
        for layer in self.svg.xpath('./svg:g[@inkscape:groupmode="layer"][@inkscape:label]'):

            label = layer.attrib["{http://www.inkscape.org/namespaces/inkscape}label"]
            animate = re.findall(r"\[(?:steps|animate)(?:=(\d+))?\]", label)






            yield Namespace(
                id = layer.attrib["id"],
                label = re.sub(r"\W+", "_", label),
                is_fixed = bool(re.search(r"fixed|\[(is_?fixe?d?|foreground)\]", label)),
                is_sticky = bool(re.search(r"background|\[(bg)\]", label)),
                no_frame = bool(re.search(r"fixed|\[(merge|no_frame|exclude)\]", label)),







>
>
>
>
>







353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371

    def get_layers(self):
        """ Per default all layers are exported, label tags [bg], [exclude], [merge] yield special treatment """
        for layer in self.svg.xpath('./svg:g[@inkscape:groupmode="layer"][@inkscape:label]'):

            label = layer.attrib["{http://www.inkscape.org/namespaces/inkscape}label"]
            animate = re.findall(r"\[(?:steps|animate)(?:=(\d+))?\]", label)
            
            # all-merge mode?
            is_export = bool(re.search(r"background|fixed|\[(bg|export|foreground)\]", label)),
            # no_frame = … or self.options.default_merge
            # do_merge = self.options.default_merge and not is_export

            yield Namespace(
                id = layer.attrib["id"],
                label = re.sub(r"\W+", "_", label),
                is_fixed = bool(re.search(r"fixed|\[(is_?fixe?d?|foreground)\]", label)),
                is_sticky = bool(re.search(r"background|\[(bg)\]", label)),
                no_frame = bool(re.search(r"fixed|\[(merge|no_frame|exclude)\]", label)),
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
        )
        # return modified SVG tree
        inkex.base.SvgOutputMixin.save(self, self.options.output)

def animate_with_values(func):
    """
    Decorator: unpacks values=1;2;3 into to= invocation of chained apply() steps.
    The time= parameter is evenly split across partial applicators (the assumption
    being that pace() already coralls e.g. keyTimes= into linear intervals.
    (Has to be defined prior AnimationSteps.)
    """
    @functools.wraps(func)
    def wrapped(self, anim, **attrib):
        if "values" not in attrib:
            return func(self, anim, **attrib)

        # prepare multiple applicators, with keyTimes pre-mapped into indexable range







|
|
<







429
430
431
432
433
434
435
436
437

438
439
440
441
442
443
444
        )
        # return modified SVG tree
        inkex.base.SvgOutputMixin.save(self, self.options.output)

def animate_with_values(func):
    """
    Decorator: unpacks values=1;2;3 into to= invocation of chained apply() steps.
    The time= parameter is evenly split across partial applicators - the assumption
    being that pace() already coralls keyTimes= into linear intervals.

    """
    @functools.wraps(func)
    def wrapped(self, anim, **attrib):
        if "values" not in attrib:
            return func(self, anim, **attrib)

        # prepare multiple applicators, with keyTimes pre-mapped into indexable range