Spline From Branche
Starting from version 2.1 of Animation Nodes, the Splines From Edges node has a new method called Spline Per Branch which minimizes discontinuity by combining the vertices of each branch in a single spline. Below is a comparison between the Splines Per Edge (left) and the Splines Per Branch (right) methods:
The author suggested using the skin modifier to solidify the filtered edges from the referenced answer. Most of the work is handled by Blender's skin modifier. This, however, imposes certain difficulties. Each of the following sections will concert with one of those difficulties.
As a study case, we shall use the example from the referenced answer. This example filters the edges by Freestyle Marked Edges such that the node tree only outputs the edges that are marked as freestyle:
You might have noticed by now that we can't output the filtered edges directly, this is due to the fact that the skin modifier will also skin individual vertices, that is, vertices that are not connected to any other vertices through edges. Such vertices were introduced because their edges may have been filtered (removed), so we should also filter vertices so as to only keep the vertices that are used by the edge list.
I will first initialize a boolean list of length
number of vertices that we will use to filter the vertices where the the corresponding boolean to each vertex is True if it was mentioned in one of the edges and False otherwise. I chose to initialize all the booleans to be False by default, the significance of this choice will become apparent in a moment. The code to generate this list is:
Next, I will loop over the filtered edges, set the booleans at the indices they carry to True and reassign the list. After the loop is done, all the elements that are referenced in the edges will be true and we will have our filtered list. The implementation is simple and is very similar to the
loops iterator loop in my answer in the referenced question, so head over there if you want more detailed explanations:
And you can see that the vertices we have are indeed the ones that are part of edges:
The foregoing section introduces a problem, since we removed some vertices, the vertices indices changed and subsequently the edge indices are no longer valid or connect two entirely different vertices together. To fix this, we have to find the new index of each vertex after the removal of the vertices. We can find a function that maps the old indices to the new ones by studying what I like to call the Indices Train:
The upper row represents the original vertices, each have its own index. The middle row represents the filtered vertices, where you can see, some of vertices were removed. The last row represents our boolean filter list. If you notice, the indices of some of the vertices changed:
- Blue: Became 1 instead of 2.
- Orange: Became 2 instead of 4.
- Green: Became 3 instead of 5.
I want you to take your time and try to figure a possible formulation for the function that maps the old indices to their new ones. The solution is simple:
Each index is reduced by some integer
n is the number of removed vertices before that index. For instance, Blue was reduced by 1 because only one vertex was removed before it, that is, the cyan one. Orange and Green were reduced by two because two vertices were removed before it, that is, the cyan and the violent one.
So the problem reduced to finding the number of removed vertices before each vertex. Notice that we have a boolean list that tells us exactly which vertices was removed, considering True to be 0 and False to be 1 (Contrary to the normal boolean representation), if for each element, we sum all the booleans before it, we will get exactly how many vertices was removed. For Blue, we have
0+1 = 1 as expected, for Orange, we have
0+1+0+1=2 as expected, and finally for Green, we have
0+1+0+1+0=2 as expected. We can implement this by using a loop:
We create an initially zero integer, at each iteration, we reassign it to be itself plus
not boolean, the
not is to implement the statement "considering True to be 0 and False to be 1". And then we append the integer to an integer list. Take your time to understand why this work.
All we have to do now is to subtract those from the old edge indices to get the new ones:
The New Edges loops over the edges, get the corresponding number of removed vertices from the Removed vertices loop, subtract it from its index and create a new edge indices from the result of the subtraction. Ending up with a new valid edge indices list. By outputting the filtered vertices and the new edges, we get the mesh that should be skined:
If we attempt to add a skin modifier now to get the skined mesh, we will be disappointed; because it will not work, the reason is explained in the following section.
The skin modifier requires one vertex to be a Root. A root is basically a vertex that blender uses as a reference to compute orientations for the output mesh. Upon adding a skin modifier manually, blender automatically adds the vertex at index zero as root, that's why you nearly never have to set the root unless needed. However, Animation Nodes keeps writing a new mesh to the object every execution, and since the skin modifier was already added in some previous execution, those new meshes don't have roots simply because nobody set them, this, in turn, cause the skin modifier to fail. The solution is simple, we just have to set the root to some vertex (A process that may be arbitrary if no fine tuning is required). Well, not really. You see, blender store the skin data in the
data.skin_vertices data collection and as it turns out, blender doesn't allow us to create such data collection. So the only way to get this to work is by removing the skin modifier and adding it again, something that was described by zeffi in this question. Another method would be using the
bpy.ops.mesh.customdata_skin_add operator which require context overwrite, something that we shouldn't really be doing in Animation Nodes. To remove the modifier and add it again, we can use the script:
#If there is a skin modifier by the name "ANSkin", remove it.
if 'ANSkin' in object.modifiers:
mod = object.modifiers['ANSkin']
#Add a skin modifier called "ANSkin".
mod = object.modifiers.new(type='SKIN', name='ANSkin')
#Edit the options of the modifier:
mod.use_x_symmetry = False
#Output the input object
outObject = object
If you are not familiar with the script node, please study this first.
We now have a working skin modifier:
One thing we can do is control the radius of each of the edges, which we can do using such script:
for v in object.data.skin_vertices.data:
v.radius = radius, radius
Which when used:
Notice that we can control the radius per vertex, so one can use such script:
for v, r in zip(object.data.skin_vertices.data, radii):
v.radius = r, r
radii is a float list with the radii, which is used as a random list, returns:
Notice that we can control the radius as two individual values in each direction, normal and cotangent to the edge. I will let you figure this out yourself.
Blend file for study and practice: