I’m trying to get 5 points equally spaced on a curve. I already searched through the API, but as i don’t have a clue where to look, i was not successfull. Can anybody help me?
The only idea i have, is to set the curve as a path with the length 5 and get the positions of an object moving along it. But i think there should be a faster / cleaner solution. Maybe converting the Curve to a Polyline and get the Controlpoints?
I also think i can remember a Script Curve -> Edges, but i can’t find it.
Ok, what i decided to do is to create a dummy object, use the curve as a path and move the dummy along this path. Then i can grab each location and rotation of the dummy object and convert it to a transormation matrix that represents a plane orthogonal to the curve and with the x axis pointing in the direction of the curves twist.
as i’m not working with blender python usually, i encounter some trouble in writing this function. So i’m posting a kind of pseudocode here and hope somebody will help me with hints on translating that to Blender Python.
# ----- get the frame positions of a curve
def framePositions(curve,len):
# setting the Curves pathLength and Flag
cu.setPathLen(len)
tmpCu = Blender.Object.New("Curve","tmp-crv")
tmpCu.link(curve)
curve.setFlag(30) #Flag 30 = Path + CurveFollow
# create the dummy object
tmpOb = Blender.Object.New("Empty","tmpOb")
# make the curve the dummys parent
tmpCu.makeParent([tmpOb])
for frame in range(len):
# increase frame
# read the dummys location and rotation
# store location and rotation in the framelist
# return framelist
Ok, found another possibility. This one is quite simple, only thing is it doesn’t work till now. it’s based upon dupliframes:
import Blender
# ----- get the frame positions of a curve
def framePositions(curve,len):
tmpCu = Blender.Object.New("Curve","tmp-crv")
tmpCu.link(curve)
curve.setFlag(30)
tmpOb = Blender.Object.New("Empty","tmpOb")
tmpCu.makeParent([tmpOb])
tmpOb.enableDupFrames = 1
tmpOb.DupEnd = len
frames = []
for dpl in tmpOb.DupObjects:
frames.append(dpl[1])
return frames
# M A I N
ob = Blender.Object.GetSelected()[0]
cu = ob.getData()
frames = framePositions(cu,5)
scene = Blender.Scene.getCurrent()
for frame in frames:
empty = scene.objects.new('Empty')
empty.setMatrix(frame)
print ('--------------------')
The main part is just for testing. And testing shows, that this isn’t working the way i want it to work till now. What i want is the empties ON the curve and equally spaced along the curve. I don’t know why this is not the case, does anybody know?
I think it is because of the dupli-matrix, but the API is quite cryptic on that topic:
The first tuple item is the original object that is duplicated, the second is the 4x4 worldspace dupli-matrix.
What is the 4x4 worldspace dupli-matrix? Is it the same as an Object Orientation Matrix? If it is, why is my script not working?
I know how to read error messages, but thanks for the care. No, the script runs perfectly smooth when i select a curve - as the main part is only for testing purposes i didn’t code a check for a valid input. The defined method is going to be part of a bigger script in which it will be impossible to get a false object type for the curve input.
The thing is: it runs smooth but delivers wrong results. The Empties that are generated by the script are not on the curve. Or they are distributed from the middle of the curve to the end of the curve instead of from start to end.
I don’t think it’s a scripting issue, but one of understanding the general blender tool Dupliframes.
ok, script is progressing. it now looks like this:
#!BPY
"""
Name: 'curveFrames'
Blender: 248a
Group: 'Curve'
Tooltip: 'distributes Empties on a Curve'
"""
import Blender
# ----- get (almost?) orthogonal frame positions on a curve
def framePositions(object,numOfFrames):
# --- get the frames of a path
def getFrames(curve):
# make a dummy object that moves along the curve
tmpOb = Blender.Object.New("Empty","tmpOb")
ob.makeParent([tmpOb],1)
# set up the dupliframes for the dummy object
tmpOb.enableDupFrames = 1
tmpOb.DupEnd = numOfFrames
# get the 4x4 worldspace duplimatrix for each frame
frames = []
for dpl in tmpOb.DupObjects:
frames.append(dpl[1])
return frames
ob = object.copy()
curve = ob.getData()
curve.setFlag(31)
curve.setPathLen(numOfFrames)
frames1 = getFrames(curve[0])
curve[0].switchDirection()
Blender.Window.EditMode(1)
Blender.Window.EditMode(0)
frames2 = getFrames(curve[0])
frames = [frames2[numOfFrames-1]]
for frame in frames1:
frames.append(frame)
return frames
def main():
segments = 5
scene = Blender.Scene.GetCurrent()
object = scene.getActiveObject()
if object != None:
if object.getType() == 'Curve':
frames = framePositions(object,segments)
for frame in frames:
empty = scene.objects.new('Empty')
empty.setMatrix(frame)
Blender.Redraw()
else:
print('active Object not a curve - skipped getCurveDivisionPoints')
print("-------------------")
main()
it’s quite a hack until now, and i can’t get an empty at the start of the curve right now. But i think it should be possible.
Can anybody tell why the switchDirection() command does not affect the curve direction until i enter edit mode manually?
It seems that you’ve moved to the dupliframes approach, but you were initially trying to move an object down the path and record it’s position to place an empty. This is a similar technique used in the script to bake a bezier curve to Ipos for use in the game engine. You can check out that script here.
An empty is created and the frame changed incrementally so that an Ipo key can be inserted at that location. I seem to remember there were issues with inserting the Ipo keys that required a second empty or something, but the info is all in the thread and it probably won’t effect what you’re trying to do.
Too bad I’m not so good at scripting yet, I do have an idea to solve your problem (to get 5 points equally spaced on a curve).
First convert the curve to a mesh (currently this is done manual, can’t find the script for that) but when it’s done with a script the curve could be copied and converted to a mesh so the original curve is not touched). Than get the total edge length/distance. Like so perhaps:
lengthE = 0
for e in me.edges:
lengthE =+ e.length
print lengthE
That is the 100(%) and now divide that number with 4. Not by 5, because this is what you do when you have one point at start and one at end of the curve (= four edges/steps). The result we will call “average vertex place”.
Next we need to know the end’s of the curve, this can be done by checking which vertex is not part of two edges and than pick one vertex to start with (doesn’t mater which one). After that we start counting the length of the the vertex’s together from the start point (one by one) and when they match around the “average vertex place” we can number that vertex or add something (like a object there, because we have the coordinate).
Problem with this approach is that you don’t have the exact location of the 5 divided points, but the very close by vertex.
@funky: thanks for the reply, i think i could go back to the initial approach… have to think about it, the threadlink you posted gives a few very good hints. i have one idea left to get the first frame (the frame at the curves startpoint), if it works i’ll stay with dupliframes.
@Dune: this was one of the first things i tried, but as you can’t control the conversion from a curve to a mesh i didn’t continue working on it. I think the dupliframes approach should work, i just have to stay tuned…
ok, i’m still stuck with this. By now i think it’s not a scripting problem but a general one of dupliframes. I can’t get it right when trying to model this by hand. The startframe - the frame at the curve start point - is always missing, even if you set dupliframes by hand. Does anybody hava a solution to this?
Even more, it seems to be a general blender issue. It’s even the same in the animation. If you parent an object to a curve/path, the position that the object is starting at is not the curve start point, but the first frame along the curve.
If you have an animation with around 500 frames nobody is ever going to mention that, but if you want to have 5 frames along a path this is a serious issue. And it’s the same with the Curve to IPO script. If you set the animation length to 4 or 5 frames or something as short, you can see it.
Is it a bug? Or am i completely mistaken?
ok, it’s a very bad bad hack, but it works. The thing with the curve start point isn’t solved, i just set the pathlength to 1.000.000 to get a frame very close to the curves startpoint. This should be sufficient for the most tasks, allthough i’m not satisfied with it.
For anyone who wants to use it, heres the final script:
#!BPY
"""
Name: 'curveFrames'
Blender: 248a
Group: 'Curve'
Tooltip: 'distributes Empties on a Curve'
"""
import Blender
# ----- get (almost?) orthogonal frame positions on a curve
def framePositions(object,numOfFrames):
# --- get the frames of a path
def getFrames(curve,frameCount):
# make a dummy object that moves along the curve
tmpOb = Blender.Object.New("Empty","tmpOb")
ob.makeParent([tmpOb],1)
# set up the dupliframes for the dummy object
frames = []
tmpOb.enableDupFrames = 1
tmpOb.DupEnd = frameCount
# get the 4x4 worldspace duplimatrix for each frame
for dpl in tmpOb.DupObjects:
frames.append(dpl[1])
return frames
ob = object.copy()
curve = ob.getData()
curve.setFlag(31)
# so this is the hack for the start point
curve.setPathLen(1000000)
frames = getFrames(curve[0],1)
# get the rest of the positions along the curve
curve.setPathLen(numOfFrames)
frames2 = getFrames(curve[0],numOfFrames)
for f in frames2:
frames.append(f)
return frames
# MAIN this should be quite clear
def main():
# set the number of desired frames here:
frames = 5
segments = frames - 1
scene = Blender.Scene.GetCurrent()
object = scene.getActiveObject()
if object != None:
if object.getType() == 'Curve':
frames = framePositions(object,segments)
for frame in frames:
empty = scene.objects.new('Empty')
empty.setMatrix(frame)
Blender.Redraw()
else:
print('active Object not a curve - skipped getCurveDivisionPoints')
else:
print('no object active')
print("-------------------")
main()
i only tested this script for nurbs curves. But it should work for Bezier Curves too, and then you can have as many empties as you want.
The Script just returns a BlenderMatrix for each point on the curve. Just set that Matrices as the OrientationMatrix of the Empties, that should do the job.
pseudocodish:
- get as many frames on the curve as you want to
- for each frame:
create an empty
set the empties orientationMatrix to the actual frame
Click on getFlag to see the breakdown of how to compute bits. We can, once again, see that the documentation is wrong. The documentation specifies that there is a “Bit 0”. This is a pink elephant, no bit 0 exists in any computer anywhere. It is always bit 1. The labels for the bit count are off by one in the documentation. So bit 1 controls the 3D flag of the curve, not “Bit:0”. In theory, If I set my bit flag to 1, the 3D button turns on, if I set it to 0, the 3D button turns off. But this only works if the curve is a path, so you have to add in the CurvePath bit which is in the 8’s position as well. Giving you a grand total of 9 for your set bits. Try replacing 31 with 9 and running the script.
import Blender
from Blender import Scene
scn = Scene.getCurrent()
obj = scn.objects.active
if obj.type == "Curve":
for curve in obj.data:
for point in curve:
empty = scn.objects.new('Empty')
empty.setLocation(point.vec[1])
Edit: This is for Bezier curves. Some modification may be necessary to make it work with other curve types.
Edit 2: @LaPostal: Just realised that you could maybe replace your hack with another that would be exact.
You could add in the first empty using a modification of my little script above and instead of iterating through the points, place an empty on point 0 of curve 0.
“point” is a BezTriple object and the location of the handles are obtained by using the point.vec[] list of vectors. The central point or the knot at the start of the curve is second in the list at point.vec[1]
There can be numerous unconnected curves in a bezier curve (add more but in edit mode rather than object mode) and these are iterated through during the “for curve in obj.data” loop. If you aren’t bothered about any secondary curves, you could just place an empty at (if your curve object is called obj) obj.data[0][0].vec[1]