Inkscape.org
Creating New Extensions new export_gif | using inkex.tween.StyleInterpolator parameters and inline node adaption
  1. #1
    prof milki prof milki @culturaljuice
    *

    Ha! I found a pretext to post here.

    So, I've been expanding on a rather simple extension ★export_gif, and it's kinda working alright. The extension system is quite nifty, and the docs as helpful as the examples. Quickly wanna share some other observations and/or minor questions:

    • Made a little conversion script (meta2inx) to generate an .inx descriptor, but noticed that the Inkscape extension lookup seemingly uses a glob of `*inx` rather than `*.inx`, hence my utility script got found and complained about. An absolute edge case, just wanted to mention it.
    • Then, because the extension does usually write an output file directly, but doesn't modify the document, it usually yields an error message. Made an option now to just repost the original SVG, but would prefer to avoid that. It would be great if there was an option or dummy response <not-modified/> for Inkscape to recognize such cases. (Again, perhaps too rare to cover.)
    • Also, I've been combining two functions in one class. The faux .effect() and an Export/Save-As .save() wrapper. While this works, it's only through a workaround (argv length) that I can detect what the current invocation was.
      Btw, I just figured out to use a <param gui-hidden> to have a sane differentiation between those modes. So, not sure if that warrants special support of sorts (or combined output+effect inx descriptors will ever become a thing).
    • What I hadn't found btw, was some mention of Inkscape's environment variables; like DOCUMENT_PATH or _PROFILE_DIR. If there were ever to be more (like INKSCAPE_EXTENSION_TYPE=output), then that might prove helpful for other plugins.

    Anyway, right now it's a rather simple layer/frame-based exporting tool.

    Animation into subframes (<animate*> to gif)

    While ideally I was hoping for some lazy option like --export-render-after=0.5s, Inkscape is not quite there yet (seen lots of talk and interest about animation support).

    Thus I'm now trying to utilize the inkex module for a rudimentary transformation approach (just color changes, and scaling, moving of paths perhaps).
    And inkex.tween.StyleInterpolator looked quite useful. However, I haven't quite figured out yet how to use it. (Not sure there are any examples yet.)

    So for starters I just tried to write my own mapping. Because I figured the tween features are mostly just attribute modifiers. And thus would need some wrapping to apply start and current iterations back to the .svg tree:

        def steps(self):
    
            transforms = []
            for animation in self.svg.xpath("//animate"): #,//animateMotion,//animateTransform
                #raise Exception(animation, animation.tag, animation.attrib)
                transforms.append(
                    self.handler(animation.tag, animation.attrib, self.get_target(animation))
                )
    
            …
    
        def handler(self, tag, attrib, target):
            """ combine tween handler and style/position editing """
    
            if tag == "animate":
                inter = inkex.tween.StyleInterpolator(
                    start_value=attrib["from"],
                    end_value=attrib["to"]
                )
                attr =  attrib["attributeName"]
    
                def apply(time, css=target["attrib"], tween=inter):
                    new_style = tween.interpolate(time)[attr]
                    css["style"] = re.sub(f"(?<=\\b{attr}:)[^;]", new_style, css["style"])
    
            elif tag == "animElse":
                inter = inkex.tween.AttributeInterpolator.create_from_attribute(attrib["attributeName"])
            else:
                return lambda *x: None
    
            return apply

    That's apparently not how StyleInterpolator wants to be utilized. It complains about `'str' object has no attribute 'specified_style'`, because I was lazily just passing the <animate>'s from= and to=.

    So I'm assuming it wants a node each. And presumably the tween end_value as already prepared/copied node.
    But not quite sure, hence asking before I invest too much into that's-not-how-it's-supposed-to-work.

    • Probably should be using AttributeInterpolator.create_from_attribute() then anyway?
    • Or is there some relation to other API utility code that would ease the parameter handling + transforms written back?
      (Haven't delved enough into the rest of inkex.* really.)

    Thanks!

  2. #2
    prof milki prof milki @culturaljuice
    *

    Found some inspiration in interp.py among others. And it was indeed useful to wrap the desired attributes in some DOM nodes:

        def animate_style(self, anim, attributeName, to, **attrib):
            """ <animate to="#000000" attributeName="fill" /> """
            target = self.get_target(anim)
            #self.adapt(target, css={attributeName: attrib["from"]})
            transformed = self.adapt(
                copy.deepcopy(target),
                css = { attributeName: to }
            )
            inter = inkex.tween.StyleInterpolator(target, transformed)
            #inter = inkex.tween.AttributeInterpolator.create_from_attribute(attrib["attributeName"])
    
            def apply(time, target=target, tween=inter):
                self.adapt(target, css=tween.interpolate(time))
    
            return apply


    It's kinda working alright, with some animateTransform instructions even.

    But now I'm wondering if I can progress this beyond a really shallow motion handler (just a singular M x,y instruction).

     

    But I'm still looking for an example of how I can traverse a path.
    inkex.paths.Path.parse_string() looks useful to consume the <animateMotion path=>, but I haven't quite figured out if or how it makes the coordinates across the path segments accessible.
    I figured implementing my frame generation requires to manually shift the shape along (tween won't do). And thus figure out where a path leads, e.g. after 10%, 20%, 30% across its course. Ideally I don't want to calculate the segments and total "length" myself.

  3. #3
    prof milki prof milki @culturaljuice

    Doesn't seem like there's anything akin to getPointAtLength yet.
    But I found a workaround with pypi:svgelements, which makes it beyond trivial:

             path = svgelements.Path("M 20,20 C 50 50 550 550 Z")
             path.point(0.5)

    Which mirrors the tween usage quite well:

            def apply(time, point=point):
                dx, dy = point(time)
                self.adapt(target, transform=f"translate({dx} {dy})")

     

Inkscape Inkscape.org Inkscape Forum Creating New Extensions new export_gif | using inkex.tween.StyleInterpolator parameters and inline node adaption