spakin / SimpInkScr

Simple Inkscape Scripting
https://inkscape.org/~pakin/%E2%98%85simple-inkscape-scripting
GNU General Public License v3.0
320 stars 31 forks source link

at_end=True in animate gives error #53

Closed gavytron closed 2 years ago

gavytron commented 2 years ago

e.animate([e1,e2, e3, e4], duration=secs, begin_time=beg, key_times=[0.0, 0.0, 0.5, 0.75, 1.0], at_end=True)

line 882, in animate anim.set('values', '; '.join(vs)) TypeError: sequence item 4: expected str instance, NoneType found

spakin commented 2 years ago

I'm not able to reproduce this problem with Inkscape 1.3-dev (ab263b1, 2022-08-14) [Linux], the latest version of Simple Inkscape Scripting, and the following script:

rad = width/20
e = circle((width/2, height/2), rad, fill='yellow')
e1 = circle((0, 0), rad, fill='red')
e2 = circle((width, 0), rad, fill='green')
e3 = circle((0, height), rad, fill='blue')
e4 = circle((width, height), rad, fill='purple')

secs = 3
beg = 0
e.animate([e1, e2, e3, e4], duration=secs, begin_time=beg, key_times=[0.0, 0.0, 0.5, 0.75, 1.0], at_end=True)

Does that script work when you run it? Could you please provide a complete, failing script that I can use to investigate the crash?

gavytron commented 2 years ago

Thank you very much! I am using the latest Inkscape for Windows: Inkscape 1.2.1 (9c6d41e410, 2022-07-14)

I tried the code provided by you above and it runs very well. So maybe the problem is the SVG file I used.

The SVG file has been created by the project "Primitive" which generates a SVG from an image, automatically: https://github.com/fogleman/primitive

A minimalistic SVG, generated by Primitive, to reproduce the error is:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="844" height="1024">
<rect x="0" y="0" width="844" height="1024" fill="#735346" />
<g transform="scale(4.000000) translate(0.5 0.5)">
<ellipse fill="#160000" fill-opacity="0.501961" cx="104" cy="65" rx="54" ry="54" />
<ellipse fill="#b89a84" fill-opacity="0.501961" cx="34" cy="209" rx="47" ry="47" />
<ellipse fill="#55d58a" fill-opacity="0.501961" cx="201" cy="120" rx="34" ry="34" />
<ellipse fill="#b1afb2" fill-opacity="0.501961" cx="9" cy="33" rx="30" ry="30" />
<ellipse fill="#ffd8c7" fill-opacity="0.501961" cx="182" cy="156" rx="16" ry="16" />
</g>
</svg>

The code below finds all the ellipses inside the group, and adds an animation (to scale the radius) to each ellipse.

largest = -1
beg_secs = 0.33
for r in all_shapes():
 if r.tag == 'g': 
  for e in r: 
   #print(e.tag)
   if e.tag == 'ellipse':
    cx=e.svg_get('cx')
    cy=e.svg_get('cy')
    rx=e.svg_get('rx')
    ry=e.svg_get('ry')
    fill=e.svg_get('fill')
    opacity=e.svg_get('fill-opacity')
    if largest<0:
     largest = rx 
    e1 = ellipse((cx, cy), (0, 0),fill=fill, opacity=opacity)
    e2 = ellipse((cx, cy), (rx*0.8, ry*0.75),fill=fill, opacity=opacity)
    e3 = ellipse((cx, cy), (rx*0.9, ry*0.90),fill=fill, opacity=opacity)
    ani_duration = 1.0 + 3.0 * rx/largest
    beg_secs = beg_secs + 8/7000
    seconds_at_0_percent   = beg_secs + 0  
    seconds_at_50_percent = beg_secs + ani_duration * 0.5  
    seconds_at_75_percent = beg_secs + ani_duration * 0.75
    duration = beg_secs + ani_duration
    key_at_0 =    seconds_at_0_percent/duration
    key_at_50 =  seconds_at_50_percent/duration
    key_at_75 =  seconds_at_75_percent/duration
    beg_s = "{}s".format( beg_secs )
    duration_s = "{}s".format( duration)
    e.animate([e1,e1,e2, e3, e], duration=duration_s, begin_time='0s', key_times=[0.0, 0.0, key_at_0, key_at_50, key_at_75, 1.0], at_end=True)

if you get rid of ", at_end=True" the code runs without errors. if you leave ", at_end=True" you can reproduce the error.

Thank you

spakin commented 2 years ago

Thanks for the reproducer. I believe I now know what the problem is. Simple Inkscape Scripting uses the first object's attributes to determine what to animate. With at_end=True, the first object is e1 and contains the attributes rx, ry, stroke, fill, and opacity. With at_end=False, the first object is e and contains only the attributes rx and ry.

In the former case, Simple Inkscape Scripting tries to interpolate across the following six sets of attributes:

rx ry stroke fill opacity
0 0 black #160000 0.501961
0 0 black #160000 0.501961
43.2 40.5 black #160000 0.501961
48.6 48.6 black #160000 0.501961
54 54 None None None
54 54 None None None

The code doesn't expect to see any of those None values and therefore throws the

TypeError: sequence item 4: expected str instance, NoneType found

exception you observed.

Where do the None values come from? SVG—annoyingly, in my opinion—provides multiple mechanisms for applying styles, such as fill, to an object. Simple Inkscape Scripting does what Inkscape itself does and stores all styles using the style attribute: style="fill:#160000". Your test SVG file uses a separate attribute per style: fill="#160000". While this is valid SVG, it implies that object e has fill, stroke, and opacity attributes while e1, e2, and e3, which are generated with Simple Inkscape Scripting, have only a style attribute and None for the fill, stroke, and opacity attributes.

I believe I can modify Simple Inkscape Scripting to ignore style attributes that are None in some object instances. If I'm not mistaken, that should get your use case to work, but won't animate styles that are specified using one SVG mechanism in some objects and a different SVG mechanism in others.

spakin commented 2 years ago

BTW, thanks for the bug report, and please re-open the issue if my latest commit didn't fix the problem.