I am trying to create POP points and add them to another list of points using a Python script, but I am not sure of the best way to do this. Right now I am creating the points by filling them in a Point POP, like this:
points = op('point1').seq.pt
for i in range(0, N):
#calculate x and y ...
points[i].par.posx = x
points[i].par.posy = y
points[i].par.posz = 0
I then merge this Point POP with my other list of points using a Merge POP.
I feel like this is a very roundabout, ‘hacky’ way of doing it though, but I haven’t found any other way to do it in the docs.
I made an example to illustrate what Im trying to do, I could indeed use DAT to POP, which is probably faster than what I’m doing now, but I feel like that would still be kind of a roundabout way of doing something that should be quite simple.
hm - with the DAT approach I guess it depends on how many points you are planning to add and are all points added in a single frame or a single point added per frame?
If it’s a lower number of points, you can optimize your script a bit:
"""
CHOP Execute DAT
me - this DAT
Make sure the corresponding toggle is enabled in the CHOP Execute DAT.
"""
def onValueChange(channel: Channel, sampleIndex: int, val: float,
prev: float):
# set number of points
N = 10
# get the table DAT and clear it - outside of the loop
pointTable = op('pointTable')
pointTable.clear(keepFirstRow = True)
# create a list in which to collect all rows to be added to the table
tblRows = []
# run the loop and add the points to the table
for i in range(0, N):
#calculate x and y
x = i
y = 2 * i
# add values to tablRows list
tblRows.append([x, y, 0])
# append all rows at once
pointTable.appendRow([x, y, 0])
return
There are a bunch of benefits:
referencing the table outside the loop is more efficient than referencing it in each for loop
creating a list of lists that will then be used to append rows all at once is more efficient than adding a row at a time
Otherwise it’d be interesting what the formula in the for loop is that generates the points. You might be able to create the positions in POPs or with CHOPs increasing efficiency as well.
Im not adding that many points currently, but if I would want to in the future, would it be better to append them one by one to save memory?
The formula is quite complicated, I am basically creating a set of N vectors with equal angles between them (theta = 2 * pi / N) and then finding the intersection of the line spanned by each vector with a rectangle with the same aspect ratio as my incoming image. So I think it may be not be possible / be too complicated to do with just POPs or CHOPs. I’ll include the original code but it may be hard to understand as it is quite complicated, so no worries if you don’t feel like reading it / answering.
I’ll at least append all rows at once, that should be a good approach, so thank you for that.
#N is number of points
N = me.parent().par.N
#fragRot is short for fragment rotation and it is the angle between the x-axis and the vector which is used to generate the first point, so like a rotation offset
fragRot = math.pi * me.parent().par.Fragrot
points = op('point1').seq.pt
points.numBlocks = N
#null1 is a TOP
xRes = op('null1').width
yRes = op('null1').height
height = yRes / xRes
#Compare theta of screen diagonal to that of each vector
diagonalAngle = math.atan(yRes / xRes)
for i in range(0, N):
theta = (fragRot + i * (math.pi * 2 / N)) % (math.pi * 2)
#Figure out which border of the screen to extrapolate vector to
#Right border
if theta >= 2 * math.pi - diagonalAngle or theta < diagonalAngle:
#normalised coordinate system, where width of screen = 1 and height = yRes/xRes
#on the unit circle x and y are in range(-1, 1)
#but for normalized coordinates we need range(0, 1), so we multiply by 0.5
#and add half of the screen height
#y = ax, so a is vector slope = y / x = sin(theta) / cos(theta)
#For right border: x = width, y = (a * width + height) * 0.5
#we dont need a z value as we use an orthographic camera
x = 1
y = (math.sin(theta)/math.cos(theta) + height) * 0.5
#Top border
elif theta >= diagonalAngle and theta < math.pi - diagonalAngle:
#y = ax, so x = y/a
#x = (width + 1/a * height) * 0.5, y = height
x = (1 + math.cos(theta)/math.sin(theta) * height) * 0.5
y = yRes/xRes
#Left border
elif theta >= math.pi - diagonalAngle and theta < math.pi + diagonalAngle:
#x = 0, y = (height - a * width) * 0.5
x = 0
y = (height - math.sin(theta)/math.cos(theta)) * 0.5
#Bottom border
else:
#x = (width - 1/a * height) * 0.5, y = 0
x = (1 - math.cos(theta)/math.sin(theta) * height) * 0.5
y = 0
points[i].par.posx = x
points[i].par.posy = y
#z = 0 so all points are at same distance in POP viewer
points[i].par.posz = 0
I was a little stumped by the lack of a Script POP recently myself and have a couple times done Script TOP (with numpy copy) → TOP to POP with decent success and not bad ergonomics.
just looking back at this, for this particular case, you could use the Ray POP to achieve the same result. The Ray POP’s first input would be a Circle POP with the Normal Direction parameter set to “Radial” and the Connectivity parameter set to “Point Primitives”.
The second input to the Ray POP is a Box POP with the size equal to your required resolution or just matching the aspect ratio.
The rotation fragRot can be controlled in the Circle POP’s Rotate parameter.