Can't use ceil/floor in expression

I’m trying to write an expression to do per-character text animation in Natron (you may remember I did this recently).

In my expression I have a bit of code to offset the animation by 15 frames and now I’m trying to slow it down (usually one character appears per frame). Here’s my code:

text = Source.text.get()
letter= 0

# what frame to start triggering the write-on effect
trigger = 15

# this should ensure that the text, regardless of size, fits within 25 frames
speed = len(text)/25

if frame >= trigger:
	letter =ceil((letter-trigger)/speed)

ret= text[:letter]

I learnt that frame number must be integers and so I’m using ceil to round up the number to the nearest integer but I’m getting the following error.

ERROR: While executing script:
ret = app1.Text1.text.expression0(16, 0)
Python error:
Python exception: integer division or modulo by zero
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "<string>", line 38, in expression0
ZeroDivisionError: integer division or modulo by zero

I tried this script out in regular desktop Python 3 (substituted frame for an integer) and it worked fine but for some reason it’s not working in Natron.

example_expression.ntp (44.4 KB)

I do not know a thing about natron, but
what if you use
math.ceil

If speed is zero (len(text) < 25) you will get a division by zero.

math.ceil doesn’t work either. Here’s the error:

ERROR: While executing script:
ret = app1.Text1.text.expression0(16, 0)
Python error:
Python exception: global name 'math' is not defined
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "<string>", line 38, in expression0
NameError: global name 'math' is not defined

This is despite Natron automatically importing the math library (from math import *) for all expressions.

Shouldn’t ceil prevent this as it rounds up to the nearest integer?

As a test I tried adding 1 to some of the variables so that letter should always be above 0:

text = Source.text.get()
letter= 1

# what frame to start triggering the write-on effect
trigger = 15

# this should ensure that the text, regardless of size, fits within 25 frames
speed = (len(text)+1)/25

if frame >= trigger:
	letter =ceil((letter-trigger)/speed)

ret= text[:letter]

Still got the same zero division error.

I also modified the expression so that it always (in theory) returns an integer:

text = Source.text.get()
letter= 0

trigger = 15

if frame >= trigger:
	letter =ceil(4.5)

ret= text[:letter]

But Natron complains about numbers being integers:
TypeError: slice indices must be integers or None or have an __index__ method

try to use letter = int(ceil(4.5)), even if ceil is rounding, I suspect the result remains as floating point (i.e. it is 4.0)

1 Like

remove the assignment (letter =)

ceil((letter-trigger)/speed)

remove the ceil:

(letter-trigger)/speed

if speed is zero => division by zero

1 Like

@hellocatfood
To try to pinpoint the culprit, you could always resort to the old method by interspersing the code with print lines, like what is done here:
https://www.tutorialspoint.com/python/number_ceil.htm

Python 2 does integer division if you specify two integers so 1 / 25 is 0. Python 3 (maybe only more recent versions than 3.0) does what you expect. To make it work in either case, do:

speed = (len(text)+1) / 25.0

1 Like

Thank you all for your help. It helped solve some of my issue but I realise my programming is also flawed! I have taken a new approach and it works. Now the input text will write within a user-specified time frame:

text = Source.text.get()
letter= 0

# what frame to start triggering the write-on effect
trigger = 15

# how many frames it'll take to write the full text
length = 46

# map values. Taken from herehttps://stackoverflow.com/a/1969274
def translate(value, leftMin, leftMax, rightMin, rightMax):
    # Figure out how 'wide' each range is
    leftSpan = leftMax - leftMin
    rightSpan = rightMax - rightMin

    # Convert the left range into a 0-1 range (float)
    valueScaled = float(value - leftMin) / float(leftSpan)

    # Convert the 0-1 range into a value in the right range.
    return rightMin + (valueScaled * rightSpan)


if  frame >= trigger:
	letter = int(ceil(translate(frame-trigger, 1, length, 1, len(text))))
else:
	letter = 0

ret= text[:letter]

If this can be improved please do show me how.

example_expression.ntp (29.2 KB)