Developing a modeling script – Start to finish

March 18th, 2006 by Helge Mathee - Viewed 36400 times -




Hey all.

In this article I want to discuss the full development process of a custom modeling tool, called “EdgeExtrudePro”.

What you need:

I going to describe the full process, so be warned, it is going to be a long trip. The article is meant for the experienced scripted as well as the start-up scripting TD, but to follow the descriptions, you’ll have to know at least the fundamentals of JScript, Arrays (the multiple usages of them, look-up tables etc) and some basic vector math. I am going to explain the math used for this tool briefly, but this is not a math primer. ;-)

What you will get out of it:

This article will get you an understanding of selection checking and user-error handling. Moreover you can learn some of the basic relationships inside of 3D geometry, such as edge – polygon adjacency, polygon-island boundaries, edge-to-edge connections and some more.

Ok – so let’s start.

The name of the tool was not my idea, so I am just going to keep it that way. What the tool does really, is using a curve to cut a hole into geometry. Here’s a link to the original tool (For Maxon’s Cinema 4D) http://www.vreel-3d.de/plugins/EEPro/EElinks.html

Here’s an animated gif from the webpage above, demonstrating the functionality. We are focusing on the part with the red circle.

eeani.gif

Looking the animated gif, I can see the following steps to reach our goal:

  • Get a polygonmesh and a curve
  • Put the curve onto the surface of the mesh
  • Delete all polygons “below” the curve’s shape
  • Extrude the resulting new boundary to fit the curve

What’s really important when developing a custom tool, is to focus on what really has to be done. As you don’t know all of the steps before you start working on them, it is still good to partition the huge task into smaller tasks, and partition them again, until you have a task you can easily solve. For now, I am just going to rephrase our tasks a little bit.

  • Check the selection for a polygonmesh and a nurbscurvelist
  • ShrinkWrap the nurbscurvelist onto the polygonmesh.
  • Delete all polygons “below” the nurbscurvelist’s shape
  • Extrude the resulting new boundary to fit the curve.

Ok – sounds better. Let’s start with the first one.

1. Check the selection for a polygonmesh and a nurbscurvelist

I don’t want to use picking sessions or anything similar right now, I will simply force the user to have two objects selected. Moreover, there are certain conditions I want to be checked. They look like this:

  • The number of objects selected has to be 2
  • The first object has to be a polygonmesh
  • The second object has to be a nurbscurvelist
  • The nurbscurvelist has to be closed
  • The nurbscurvelist has to contain only one curve

Allright, putting all of this into a simple JScript function, it looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// checks if the selection is correct
function js_checkSelection()
{
	// check if we have two objects
	if(selection.count!=2)
	{
		logmessage("Please select a polygonmesh and a curvelist!");
		return false;
	}
 
	// check if the first one is a polygonmesh
	if(selection(0).type!="polymsh")
	{
		logmessage("The first object you selected is not a polygonmesh!")
		return false;
	}
 
	// check if the second object is a curvelist
	if(selection(1).type!="crvlist")
	{
		logmessage("The second object you selected is not a curvelist!");
		return false;
	}
 
	// check if the curve is closed
	if(!selection(1).ActivePrimitive.Geometry.Closed)
	{
		logmessage("The curve you selected is not closed!");
		return false;
	}
 
	// check if the curvelist contains only one curve
	if(selection(1).ActivePrimitive.Geometry.Curves.count!=1)
	{
		logmessage("The curve you selected contains more or less curves than 1!");
	}
 
	// if everything went well - just return true
	return true;
}

Allright – let’s go to number 2.

2. ShrinkWrap the nurbscurvelist onto the polygonmesh.

For the projection I am just going to use XSI’s ShrinkWrap. There’s the option to implement the projection myself, but I try to rely on existing functionality as much as possible – to speed up the implementation process. Just ShrinkWrapping is not going to do the full job though. So these are the considerations:

  • The two objects have to be in the same reference space
  • After projection / shrinking the curve onto the surface we want to freeze it, because we might change the topology the curve was shrinked onto

The only thing I really did is manually shrink-wrapping a curve onto a polygonmesh, copy & pasting the code out of the script editor history and adding some more steps. Here’s my shrinking function:

1
2
3
4
5
6
7
8
9
10
11
12
13
//apply the shrinkwrap
function js_applyShrinkWrap(curve,mesh)
{
	// if the curve is not parented to the mesh yet
	// parent it in... 
	// we have to do this to simplify the reference frames
	if(curve.parent.fullname!=mesh.fullname)
		ParentObj(mesh,curve);
	ResetTransform(curve, siCtr, siSRT, siXYZ);
	var op = ApplyOp("ShrinkWrap", curve+";"+mesh, 3, siPersistentOperation);
	SetValue(op+".proj", 6);
	FreezeObj(op);
}

3. Delete all polygons “below” the nurbscurvelist’s shape

Okay – so now this is where it gets complicated. I am going to explain how we find what needs to be deleted by partitioning the task again:

  • Find all edges which are crossing the curve
  • Find all adjacent polygons to these edges
  • Find the two boundaries of this resulting polygon island (inner and outer boundary)
  • Find out which boundary is shorter and call it the inner boundary
  • Get all of the polygons inside of the inner boundary + the polygons from the crossing edges
  • Delete them

I need a function to figure out if an edge crosses a curve. This seems to be a hard one, it’s not really though. Let’s look at it:

To find out if an edge is crossing a curve, we need to find out if the two points of the edge are on the “opposite” sides of the curve. If they are, the edge crosses the curve. To do that, I will find the closest points on the curve for the two edge-points, find their center, and the center’s point on the curve. Then I take the tangent of the curve at this position and build a plane which cuts the edge in 3D, or not. If it cuts the edge, it crosses the curve.

ee_pro_03.jpg

In the illustration, the blue line is the curve, the green grid marks the plane used for the intersection calculation and the red line is an example edge.

And here’s the javascript function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// check if an edge crosses a curve
function js_doesEdgeCrossCurve(edge,curve)
{
	// get the two positions
	var edgePos1 = edge.points(0).position;
	var edgePos2 = edge.points(1).position;
 
	// get the U value of the closest points on the curve
	var edgeU1 = curve.GetClosestCurvePosition2(edgePos1).toArray()[2];	
	var edgeU2 = curve.GetClosestCurvePosition2(edgePos2).toArray()[2];	
 
	// get the two positions on the curve of the closest points	
	var edgeOnCurve1 = curve.curves(0).EvaluatePosition(edgeU1).toArray()[0];
	var edgeOnCurve2 = curve.curves(0).EvaluatePosition(edgeU2).toArray()[0];
 
	// get the position in the center of the two edge on curves
	var edgePos = XSIMath.CreateVector3();
	edgePos.Add(edgeOnCurve1,edgeOnCurve2);
	edgePos.ScaleInPlace(0.5);
 
	// get the U value of the closest point on the curve to this one
	var edgeU = curve.GetClosestCurvePosition2(edgePos).toArray()[2];	
	// get the position of that point
	var edgeOnCurve = curve.curves(0).EvaluatePosition(edgeU).toArray()[0];
	// get the tangent of the curve at that position
	var tangent = curve.curves(0).EvaluatePosition(edgeU).toArray()[1];
 
	// get the U value of the closest point on the curve to this one
	var normal = XSIMath.CreateVector3();
 
	// make the normal the connection of the first point
	// and the position on the curve
	normal.Sub(edgePos1,edgeOnCurve);
	// cross this result with the tangent to retrieve the bi-normal
	normal.Cross(normal,tangent);
	normal.NormalizeInPlace();
	// cross this result with the tangent again to retrieve the normal
	normal.Cross(normal,tangent);
	normal.NormalizeInPlace();
 
	// define two connection vectors
	var con1 = XSIMath.CreateVector3();
	var con2 = XSIMath.CreateVector3();
 
	// the first is the connection between the edgepos1 and the position on the curve
	// resp. for the second
	con1.Sub(edgePos1,edgeOnCurve);
	con2.Sub(edgePos2,edgeOnCurve);
 
	// get the distance of the two connection vectors to the plane defined by the normal
	var dist1 = con1.Dot(normal);	
	var dist2 = con2.Dot(normal);	
 
	// if the points are on different sides of the plane
	// (if one distance is larger and the other one less than 0)	
	if( (dist1>=0 && dist2< =0) ||
		(dist1<=0 && dist2>=0))
	{
		// the edge crosses the curve
		return true;
	}
	else
	{
		// nope - it doesn't cross
		return false;
	}
}

To find all of the edges, I create a helper function which simply calles js_doesEdgeCrossCurve a couple of times, like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// returns an array of indices of the edge crossing the curve
function js_getAllCrossingEdges(meshObj,curveObj)
{
	// define an array for all of the edges crossing the curve
	var result = new Array();
 
	// get the geometry of both objects
	var mesh = meshObj.ActivePrimitive.Geometry;
	var curve = curveObj.ActivePrimitive.Geometry;
 
	// loop through
	for(var i=0;i<mesh .edges.count;i++)
	{
		// get the edge
		var edge = mesh.edges(i);
		// if it crosses, put in the result array
		if(js_doesEdgeCrossCurve(edge,curve))
		{
			result[result.length] = i;
		}
	}
 
	return result;
}

Result:

ee_pro_05_b.jpg

Now as we have all edges crossing the curve, we need to find all of the adjacent polygons. As adjacency is a simple task, I am not going to use XSI’s command for selecting adjacent components, but implement a JScript function to do the job. It is simply looping over all edges and finding all polygons adjacent to each edge.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// gets all adjacent polygons of an edge-index-array
function js_getAdjacentPolygons(meshObj,indices)
{
	// get the geometry of the meshObj
	var mesh = meshObj.ActivePrimitive.Geometry;
 
	// an array which is used as a hash-table
	// for all of the polyons used
	// example: if polyUsed[4] == true, that means
	// the polygon with the index 4 is adjacent
	var polyUsed = new Array();
 
	// loop through all edge indices	
	for(var i=0;i<indices .length;i++)
	{
		// get the edge
		var edge = mesh.edges(indices[i]);
 
		// get the collection of neighborpolygons of this edge
		// note: an edge can have only one polygon linked to it
		// that means it is a boundary edge... if that happens,
		// just return false to tell the calling function that
		// we have to stop now.
		var nbPolies = edge.NeighborPolygons();
		if(nbPolies.count&lt;2)
		{
			logmessage("We cannot cut a boundary!");
			return false;
		}
		for(var j=0;j<nbPolies.count;j++)
		{
			polyUsed[nbPolies(j).index] = true;
		}
	}
 
	// define the result array
	var result = new Array();
	for(var index in polyUsed)
	{
		result[result.length] = index;
	}
 
	return result;
}

Result:

ee_pro_06_b.jpg

To find the two boundaries and determine which one is shorter, I loop through all edges of the polygons I just got from the adjacency function and count their occurences. If I find an edge which is used only once, it is a boundary of the polygon-island. To find the two different boundaries, I start with one edge, mark it as non-boundary and find the next connected edge which is a boundary. I repeat that step until I reach the edge I started with. Those edges all together are one boundary (either the inner or the outer one – I don’t know that yet). Now I can just take all of the edges and define them as “the other” boundary. By comparing the number of elements in both boundaries, I can find the shorter one and define it as “the inner boundary”.

First – I need a function which tells me if two edges are connected or not:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// returns true if two edges are connected
function js_areEdgesConnected(edge1,edge2)
{
	// if the edges are the same, we don't call them "connected"
	if(edge1.index==edge2.index)
	{
		return false;
	}
 
	// compare all points of the one edge with
	// all points of the other one
	// if there is any match, the edges are connected
	if(
		(edge1.points(0).index==edge2.points(0).index) ||
		(edge1.points(0).index==edge2.points(1).index) ||
		(edge1.points(1).index==edge2.points(0).index) ||
		(edge1.points(1).index==edge2.points(1).index))
	{
		return true;
	}
	return false;
}

Second – I need a function which does what I described above: Find the shorter (inner boundary) and its elements:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// returns the shortest edge boundary of a given polygon index array
function js_getShortestEdgeBoundary(meshObj,indices)
{
	// get the geometry of the mesh
	var mesh = meshObj.ActivePrimitive.Geometry;
 
	// this is a hash-table which counts the usage of edges
	// if the counter is one, the edge is a boundary(!)
	var edgeCount = new Array();
 
	// loop through all given polygons by index
	for(var i=0;i<indices .length;i++)
	{
		// get the polygon (called facet here)
		var facet = mesh.facets(indices[i]);
 
		// get the polygons edges
		var edges = facet.edges;
		for(var j=0;j<edges.count;j++)
		{
			// put the index of the edge into the edgeCount array
			// if it is not there yet, otherwise increment the number there
			if(!edgeCount[edges(j).index])
				edgeCount[edges(j).index] = 1;
			else
				edgeCount[edges(j).index]++;
		}
	}
 
	// define two arrays for the boundaries
	var boundary1 = new Array();
	var boundary2 = new Array();
 
	// now we start by putting the first boundary edge into the boundary1 array
	for(var index in edgeCount)
	{
		// if the edge is a boundary
		if(edgeCount[index]==1)
		{
			// put the edge as the first element in the boundary1 array 
			boundary1[boundary1.length] = index;
			// set the edge to be nothing / so it looks like it is not a boundary anymore
			// because we want to use it only once
			edgeCount[index] = 0;
			// and leave the loop afterwards...
			break;
		}
	}
 
	// check again if we have an element
	if(boundary1.length!=1)
	{
		logmessage("This should never happen... something really bad.");
		return false;
	}
 
	// now we try to add edges to the boundary until we hit the start again
	// we definitively have to leave if we have more edges than exist
	// this is to make sure we don't loop infinitely for some reason
	while(boundary1.length<mesh.edges.count)
	{
		// get the last edge in the boundary
		var lastEdge = mesh.edges(boundary1[boundary1.length-1]);
 
		// check if the boundary is alreay closed
		// the shortest possible boundary has 3 edges,
		// so only check if we have at least 3
		if(boundary1.length>2)
		{
			// the first edge in the boundary
			var firstEdge = mesh.edges(boundary1[0]);
			// if the edges are connected, the boundary is closed.
			if(js_areEdgesConnected(firstEdge,lastEdge))
			{
				// yeah - closed - we leave this loop.
				break;
			}
		}
 
		// try to find the next edge which is connected to the last one
		// in the boundary... init it with -1, so we can check later if we really found one
		var newIndex = -1;
 
		// loop through all edges we counted
		for(var index in edgeCount)
		{
			// if the edge is a boundary 
			// note that we set used edges to 0, so we don't use them more than once
			if(edgeCount[index]==1)
			{
				// get this edge
				var currentEdge = mesh.edges(index);
 
				// if these two edges are connected, we add it to the boundary
				if(js_areEdgesConnected(currentEdge,lastEdge))
				{
					// set the newindex to the current index
					newIndex = index;
 
					// and leave this loop!
					break;
				}
			}
		}
 
		// if the newIndex is still -1, we didn't find a new edge,
		// which is really bad - so we leave the whole function and give up
		if(newIndex==-1)
		{
			logmessage("Something bad happened. I give up.");
			return false;
		}
 
		// add the newIndex to the boundary
		boundary1[boundary1.length] = newIndex;
		// remove it from the edgeCount array,
		// so we don't use it again
		edgeCount[newIndex] = 0;
	}
 
	// so now we have the first boundary - the other boundary simply
	// consists of all of the boundary edges left, so:
	// loop through the edgeCount array and get all edges with count1
	// and put them in boundary2
	for(var index in edgeCount)
	{
		// is it a boundary?
		if(edgeCount[index]==1)
		{
			boundary2[boundary2.length] = index;
		}
	}
 
	// do a last check if both boundaries are valid
	if(boundary1.length&lt;3 || boundary2.length&lt;3)
	{
		logmessage("The boundaries are too short - that's bad - I give up");
		return false;
	}
 
	// so now we know both boundaries, we check which one contains less edges
	if(boundary1.length<boundary2 .length)
		return boundary1;
	else
		return boundary2;
}

Result:

ee_pro_08_b.jpg

Now we have the polygons crossing the curve and the inner boundary. Another function will now get all polygons to be deleted. It basically does a “grow selection”, but with the following rule: We can only grow the selection over edges which are on the inner boundary. That way we get all polygons “inside” the boundary – not the ones outside of the polygons crossing the curve.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// adds all polygons inside of a polygonselection to a given array
// by using a given edgeboundary
function js_addAllInsidePolygons(meshObj,polyIndices,edgeIndices)
{
	// get the geometry of the meshObj
	var mesh = meshObj.ActivePrimitive.Geometry;
 
	// put all polygons in a "used" hashtable
	var polyUsed = new Array();
	for(var i=0;i<polyindices .length;i++)
	{
		polyUsed[polyIndices[i]] = true;
	}
 
	// put all the edges in a "used" hashtable
	var edgeUsed = new Array();
	for(var i=0;i<edgeIndices.length;i++)
	{
		edgeUsed[edgeIndices[i]] = true;
	}
 
	// now loop through all polygons and
	// find all neighbor polygons, but(!)
	// only the one which are connected by edges
	// edgeIndices array
	// so we get only the ones on the right side
	for(var i=0;i<polyIndices.length;i++)
	{
		// get the polygon (called facet)
		var facet = mesh.facets(polyIndices[i]);
 
		// get the edges of the polygon
		var edges = facet.edges
 
		// loop through all edges and checked if they are used
		for(var j=0;j<edges.count;j++)
		{
			if(edgeUsed[edges(j).index])
			{
				// get the two polygons of the edge
				var nbFacets = edges(j).neighborPolygons();
 
				// if the edge has more than one polygon
				// (if not - we only counted it, otherwise how would we
				// be here, huh?)
				if(nbFacets.count>1)
				{
					// for each nb-facet
					for(var l=0;l&lt;2;l++)
					{
						// if the facet is not used yet
						if(!polyUsed[nbFacets(l).index])
						{
							// put it as used and add it to the polyIndices array
							polyUsed[nbFacets(l).index] = true;
							polyIndices[polyIndices.length] = nbFacets(l).index;
 
							// afterwards put all edges of the new polygon into the edgeUsed array
							var nbFacetEdges = nbFacets(l).edges;
							for(var k=0;k<nbfacetedges .count;k++)
							{
								edgeUsed[nbFacetEdges(k).index] = true;
							}
						}
					}
				}
			}
		}
	}
 
	// now get all used polygons and return them as an array
	var result = new Array();
	for(var index in polyUsed)
	{
		result[result.length] = index;
	}
 
	return result;
}

Result:

ee_pro_09_b.jpg

4. Move the resulting new points onto the curve

The extrude is going to be done by the main function, more about that later. For now – I just write a function which takes all of the points above a specific index and moves them onto the curve by closest point. For this it is really important that we froze the curve earlier, just in case we need to delete the polygons we shrunk onto.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// moves all points of a mesh starting with the given index onto the curve
function js_movePointsOntoCurve(meshObj,curveObj,startIndex)
{
	// get the geometries of the objects
	var mesh = meshObj.ActivePrimitive.Geometry;
	var curve = curveObj.ActivePrimitive.Geometry;
 
	// loop through all points starting with the given index
	for(var i=startIndex;i<mesh .points.count;i++)
	{
		var oldPos = mesh.points(i).position;
 
		// get the U value of the closest point on the curve
		var newPos = curve.GetClosestCurvePosition2(oldPos).toArray()[3];	
 
		// now move the point
		Translate(meshObj+".pnt["+i+"]", newPos.x,newPos.y,newPos.z, siAbsolute, siParent, siObj, siXYZ);
	}
}

To finish up the tool, we end by creating a main function which calls all of our helper functions one after another.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// the main function to run the tool
function js_main()
{
	// check the selection
	if(!js_checkSelection())
	{
		return false;
	}
 
	// define our variables (we can because the selection was checked before)
	var mesh = selection(0);
	var curve = selection(1);
 
	// apply the shrink wrap...
	js_applyShrinkWrap(curve,mesh);
 
	// get the crossing edges..
	var edgesCrossing = js_getAllCrossingEdges(mesh,curve);
	if(edgesCrossing.length==0)
	{
		logmessage("There are no crossing edges!");
		return false;
	}
 
	// get the adjacent polygons
	var poliesOnCurve = js_getAdjacentPolygons(mesh,edgesCrossing);
	if(!poliesOnCurve)
	{
		// something didn't work - we have to abort.
		return false;
	}
 
	// now get the shortest edge boundary
	var edgeBoundary = js_getShortestEdgeBoundary(mesh,poliesOnCurve)
 
	// now get all polygons inside that boundary as well
	var polygonsToDelete = js_addAllInsidePolygons(mesh,poliesOnCurve,edgeBoundary);
 
	// select the adjacent edges of the polygons
	SelectAdjacent(mesh+".poly["+polygonsToDelete+"]", "Edge", false);
 
	// delete all polygons... finally something's happenin' !
	ApplyTopoOp("DeleteComponent", mesh+".poly["+polygonsToDelete+"]", siUnspecified, siPersistentOperation, null);
 
	// now we have the edges still selected, as we selected the adjacent ones before,
	// what we do now is remember the pointcount - as the points we have to move are the
	// new ones after the extrude operation
	var pointCount = mesh.ActivePrimitive.Geometry.Points.Count;
 
	// now duplicate the edges (if we don't specify anything it is going to be the selection
	DuplicateMeshComponent(null, siPersistentOperation);
 
	// now move all points on the mesh onto the curve starting with the given index
	js_movePointsOntoCurve(mesh,curve,pointCount);
}

Result:

ee_pro_11_b.jpg

Ressources

Here’s the final script: Edge Extrude Pro
Moreover, here is a camtasia capture of working with it: Camtasia Demo (Techsmith, 9 MB)

cheers!

20 Responses to “Developing a modeling script – Start to finish”

  1. Gene Crucean says:

    Great stuff Helge. Thanks.

  2. Eternal says:

    Great explanation .. Great Tool .. Thank you

  3. el_diablo says:

    Very nice walkthrough, and a nice tool too have. I just wish in-viewport interface was more easily programmable than using CDH. I think this would make more interactive tools possible.

  4. Bullit says:

    Amazing to see the logic and steps behind. Thanks

  5. ShadowM8 says:

    Great article, thanks a lot!

  6. Zo says:

    That”s just i was looking for !!! 10x

  7. Oz Adi says:

    Great stuff Helge! Is there going to be a PART II of “Getting Started with Scripting in XSI” DVD?

    you talked about it in the first one… I hope you”ll find the time to make it.

  8. PJ Torrevillas says:

    Thanks Helge,

    Great explanation and break down. Great read.

    PJ

  9. Helge Mathee says:

    Oz: Hopefully there will be. I can”t tell any details yet though…

  10. Filippo says:

    Many thanks for each single line of code ( and comment )

    Great stuff.

    FL

  11. Agedito says:

    Bravo!!

    Very usefull and interesting! :D
    Thanks!!

  12. Great stuff. Thx !! :)

  13. michael isner says:

    This was an awesome tutorial. I really enjoyed stepping through the logic process here.
    Very clear and I think very helpful for people interested in cg problem solving and the XSI SDK.

    Opens the door to all kinds of interesting procedural geometry experiments for many people I”m sure…

    Michael

  14. awesome.. since im an early stage usin xsi.. hope someday those scripts really a meaningful use to me :)
    Cheers..~

  15. Joel Van Eenwyk says:

    Awesome article! Thanks!

    ~Joel

  16. Meng Yang Lu says:

    Helge,

    Did you get bored at work again? :P

    Fantastic tutorial. Thanks for teaching us.

    peace,

    Lu

  17. DavidF says:

    Great tute! The detailed comenting is very illuminative.
    Usefull tool too as icing on the cake.
    Thank you, Helge.
    It was a pleasure to read.

  18. Garfield says:

    Great, man! Need more XSI scripting tuts! (*Especially* with tha lack of plug-ins for XSI :( )
    10x, again

  19. Jz Bescansa says:

    Awesome tutorial. Thanks for share!

  20. Szabolcs Matefy says:

    Helge,

    It looks like that the shape changes when the shrinkwrap applies. What if the shape is converted to polymesh, that polymesh is shrinkwrapped with follow vertex normal bidirectional, and the border edges would be converted into spline again?

    Just a small idea