Syntax problem of For / While loops or Imperative block

Hi All,

My objective is to produce a correctly ordered and directed sequence of lines.

The input lines have draw / selection order that is random and line directions are also inconsistent. I also input the start point of the sequence of lines.

Problem: The logic works OK when stepped through with 3 lines, but the same process does not work with For / While loops in an Imperative block. It seems that syntax is wrong. Can anyone see a mistake?

Outline of my Method:

  1. Find the line closest to the known start point
  2. Orient that line so it starts at the known start point
  3. Iteratively find the next nearest line
  4. Orient each next line to continue the path
  5. Repeat until all lines are ordered and directed

Things I tried already:

  • Changed Line.StartPoint(remainingLines) to remainingLines.StartPoint
  • Changed List.IndexOf to DSCore.List.IndexOf, etc.
  • Changed Geometry.DistanceTo to Autodesk.Geometry.DistanceTo
  • Changed test ? true : false to an if-else block (and back again)

C3D_SortLineOrderByNearest.dyn (40.4 KB)

C3D_SortLineOrderByNearest_sample.dwg (986.9 KB)

This might be relevant to your interests:

Awesome! Thank you, @jacob.small :heart_exclamation: I will give that a try later.

However, I need to use For / While loops for some other tasks, so I need to learn why my loops don’t work.

The biggest benefit of design script is that you RARELY need a for or a while loop, as the tool will iterate over a list for you. The exceptions to this are situation where an action has to be performed repeatedly until a condition is met. In your case it likely could be done via a custom node without any design script. However while they are rare; they exist and a path has to be found. For this reason imperative code block statements were introduced.

However the method of authoring means many issues get completely suppressed, and you have a lot less control over output. It’s IMPERATIVE that the code returns a value, and so even if it returns ‘null’ due to a warning it’s going to continue as if it didn’t hit anything.

As a general rule when writing imperative code:

  1. Once you start imperative code you cannot use associative methods anymore. This means no lacing or list levels will work - you have to specify the for loops directly (like I did in the Python).
  2. Once you enter imperative code warnings will often be suppressed which means you’re going to just receive ‘null’ as an input.
  3. To return something it has to have been defined outside of any nested loops.

Likely your Line.StartPoint(remainingLines); and remainingLines.StartPoint are both failing due to number 1 above as remainingLines is a list not a geometry and you’re in an imperative code block so it can’t be iterated over.

This definnition should perform what you want, with extra annotations to make it clear what I was thinking when:

def SortToChain(curves: DesignScript.Curve[]) //defines a new definition
//opens the defintion block
{
	//defines an imperative code block called orderedLines
	orderedLines = [Imperative]
	//starts the imperative code, allowing for and while loops but blocking
	//list levels and replication guides
	{
 		//get the first curve from the curves list as crv
		crv = curves[0];
		//remove the first curve
		crvs = List.DropItems(curves,1);
 		//define net as a list containing the first curve
		net = [crv];

		while (List.Count(crvs)>0) //define a loop while more than 0 curves
		//start the while loop
		{
			//get the start and end point of the curve
			start = crv.StartPoint;
			end = crv.EndPoint;
			//define dists as an empty list
			dists = [];
			//starting a for loop now as crvs.StartPOint will fail as it can't
			//iterate over a loop
			//define a loop for each next in the crvs list 
			for (next in crvs)
			//start the for loop
			{
				//get the distance from start and end to next's start and end
				sS = start.DistanceTo(next.StartPoint);
				sE = start.DistanceTo(next.EndPoint);
				eS = end.DistanceTo(next.StartPoint);
				eE = end.DistanceTo(next.EndPoint);
				//combine the distances into a single list
				measures = [sS,sE,eS,eS];
				//get the minimum distance found
				measure = List.MinimumItem(measures);
				//add the minimum distance to the list of distances
				dists = List.Join([dists,measure]);
			//close the for loop
			}
			//sort the curves list by the distances list
			crvs = List.SortByKey(crvs,dists)["sortedList"];
			//get the first curve from the curves list as crv
			crv = crvs[0];
			//remove the first item from the curves list
			crvs = List.DropItems(crvs, 1);
			//add the new value of crv to the net list
			net = List.AddItemToEnd(crv,net);
		//close the while loop
		}
		//return net from the orderedLines imperative code block
		return net;
	//close the impertative code block
	}
	//return orderedLines from the SortToChain definition block
	return orderedLines;
//close the definition code block
};

Brilliant! The concept of [associative] and [imperative] methods is still new to me, so that really helps me. Thank you! (Tack så mycket!) :man_bowing:

Likely your Line.StartPoint(remainingLines); and remainingLines.StartPoint are both failing due to number 1

Yes, that was causing the problem. I can that see your solution replaces them with For loops.

I ran and referenced your code. The annotations really helped. :grinning_face: Below is the code that I will adopt. It is basically that same a yours with a small adjustment to change the line direction.

def SortLinesByNearest(
    curves: DesignScript.Curve[],
    startPoint: DesignScript.Point)
{
    return = [Imperative]
    {
        unprocessedLines = curves;
        lineCount = DSCore.List.Count(unprocessedLines);
        currentEndPt = startPoint;
        sortedLines = [];

        for (index in 0..(lineCount - 1))
        {
            nearestDists = [];
            boolReverseLine = [];

            for (candidateLine in unprocessedLines)
            {
                distToStartPt =
                    Autodesk.Geometry.DistanceTo(candidateLine.StartPoint, currentEndPt);

                distToEndPt =
                    Autodesk.Geometry.DistanceTo(candidateLine.EndPoint, currentEndPt);

                boolStartCloser = distToStartPt < distToEndPt;
                nearestDist = boolStartCloser ? distToStartPt : distToEndPt;

                nearestDists = List.Join([nearestDists, nearestDist]);
                boolReverseLine = List.Join([boolReverseLine, boolStartCloser]);
            }

            linesSortedByDist =
                List.SortByKey(unprocessedLines, nearestDists)["sortedList"];

            reverseBoolSortedByDist =
                List.SortByKey(boolReverseLine, nearestDists)["sortedList"];

            closestLine = linesSortedByDist[0];

            orientedLine =
                reverseBoolSortedByDist[0]
                ? closestLine
                : closestLine.Reverse();

            sortedLines =
                DSCore.List.AddItemToEnd(orientedLine, sortedLines);

            currentEndPt = orientedLine.EndPoint;
            unprocessedLines =
                DSCore.List.DropItems(linesSortedByDist, 1);
        }

        return = sortedLines;
    };
};

I also did some speed tests with TuneUp to decide which of the following methods to adopt for my code. As you can see the difference is minimal:

  • For < While : approx. 5-15 ms less processing time
  • List.SortByKey < List.IndexOf : approx. 10 ms less processing time

Glad it helped. Good work!