Jump to content
Developer Wiki and Function Reference Links ×

Loop through selected objects


NytSkab

Recommended Posts

I am trying to draw rectangle around bounding box of selected objects. See script below:

PROCEDURE GroupBB;
VAR	
X1,Y1,X2,Y2:Real;
H1:HANDLE;

BEGIN
	H1:=FSActLayer;
	WHILE (H1 <> NIL) DO BEGIN
		GetBBox(H1,X1,Y1,X2,Y2);
		AlrtDialog(Concat(GetTypeN(H1),' - ',X1,' - ',Y1,' - ',X2,' - ',Y2));
        	Rect(X1,Y1,X2,Y2);
		H1:=NextSObj(H1);
	END;
END;

Run(GroupBB);

However, the script starts an infinite loop. What is wrong?

Link to comment

The reason you are getting an infinite loop is that you are creating a rectangle, which would be selected after creation, with every iteration of your loop.  So there is always a selected object to use NextSObj on.

 

Are you looking to create one rectangle around all selected objects or a rectangle around each single selected object?  Right now, your script will do the latter.

 

Depending on what your aim is here, there are different solutions.

 

Draw one rectangle around all selected objects:

This is actually very simple (with some caveats). 

  1. Since the objects are all selected, you can use the Group procedure to group them all together
  2. Then do H1:=LActLayer; to get the handle to the group
  3. Use the GetBBox procedure to pull the points of the bounding box
  4. Create your rectangle
  5. Use HUngroup to ungroup your selection

The major caveat is that, if objects are spread across different layers, the group command will force them all onto one layer together.  If this is the case, I would use ForEachObject or something similar to the code you have to go through the selection and build DYNARRAYs of HANDLEs to both the objects themselves and their parents (in this case, the layer they live on) using GetParent.  Then you can use a FOR loop to go through the array and reassign the objects to their layers using SetParent.

 

A code sample to do this, including resetting things on multiple layers, is included below.

 

PROCEDURE BoxAllTheThings;

VAR

	objCount:INTEGER;
	objArr,parentArr:DYNARRAY OF HANDLE;

PROCEDURE BuildArrays(h:HANDLE);

	BEGIN
		objCount:=objCount+1;
		ALLOCATE objArr[1..objCount];
		ALLOCATE parentArr[1..objCount];
		objArr[objCount]:=h;
		parentArr[objCount]:=GetParent(h);
	END;
	
PROCEDURE CreateBox;

	VAR
	
		h:HANDLE;
		A,B:POINT;

	BEGIN
		Group;
		h:=LActLayer;
		GetBBox(h,A.x,A.y,B.x,B.y);
		HUngroup(h);
		
		Rect(A.x,A.y,B.x,B.y);
	END;
	
PROCEDURE RestoreParents(objects,parents:DYNARRAY OF HANDLE; iCount:INTEGER);

	VAR
	
		i:INTEGER;
		BSB:BOOLEAN;

	BEGIN
		FOR i:=1 TO iCount DO BSB:=SetParent(objects[i],parents[i]);
	END;
	
BEGIN
	objCount:=0;
	ForEachObject(BuildArrays,(SEL=TRUE));
	IF(objCount>0) THEN 
		BEGIN
			CreateBox;
			RestoreParents(objArr,parentArr,objCount);
		END;
END;

Run(BoxAllTheThings);

 

Draw rectangles around each selected object:

This one is a bit more complicated.

  1. Use either your existing WHILE loop code or ForEachObject to generate a DYNARRAY of HANDLE of all selected objects.
  2. Use a FOR loop to go through the array.
  3. For each handle, use GetBBox to poll the coordinates
  4. Create a rectangle using the coordinates

Below is a code sample of how I would approach it.  I prefer to use ForEachObject when doing stuff like this, it gives you a lot more control of which objects are affected (targeting selected objects only on the active layer for example).

 

PROCEDURE BoxTheThings;

VAR

	objCount:INTEGER;
	objArr:DYNARRAY OF HANDLE;
	
PROCEDURE BuildArray(h:HANDLE);

	BEGIN
		objCount:=objCount+1;
		ALLOCATE objArr[1..objCount];
		objArr[objCount]:=h;
	END;
	
PROCEDURE CreateBoxes(objects:DYNARRAY OF HANDLE; iCount:INTEGER);

	VAR
	
		A,B:POINT;
		i:INTEGER;
		
	BEGIN
		FOR i:=1 TO iCount DO
			BEGIN
				GetBBox(objects[i],A.x,A.y,B.x,B.y);
				Rect(A.x,A.y,B.x,B.y);
			END;
	END;
	
BEGIN
	objCount:=0;
	ForEachObject(BuildArray,(SEL=TRUE));
	IF(objCount>0) THEN CreateBoxes(objArr,objCount);
END;

Run(BoxTheThings);

 

  • Like 1
Link to comment

That would be simple.  Using my second example, change the ForEachObject criteria so that the line reads as ForEachObject(BuildArray,((SEL=TRUE) & (T=GROUP)));

 

That's one of the reasons I prefer to use ForEachObject, it really gives you a lot of control in terms of which objects to run procedures on.  Writing out the criteria sections can sometimes get tricky, and there used to be a nasty bug where if you were missing a parenthesis, compiling would cause a memory leak and crash Vectorworks (thereby throwing away all of your hard work), so if I need a complicated selection I will use the button in the upper left of the Script Editor window and select Criteria to have Vectorworks build the correct criteria string.

  • Like 1
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...