Jump to content

Event enabled plugins with variable number of parameters


Recommended Posts

I am attempting to create a point object plugin that generates a grid of boxes based on variable inputs for number of rows and number of boxes in each row.

I have managed to make the plugin draw the grid after a dialog has been opened however I can't figure out how to get it to draw the grid on creation.

I have the plugin set to event enabled with resets on for both move and rotate.

 

I believe my issues are stemming from overriding the default behaviour for object events.

Here is my program

PROCEDURE testing;
CONST
	{Event ID's}
	kEventID_ParametricRecalculate = 3;
	kEventID_OnObjPref = 4;
	kEventID_OnInitProperties = 5;
	kEventID_OnDoubleClick = 7;
	kEventID_OnUIButtonHit = 35;
	kEventID_OnAddState = 44;
	
	{Notification ID's}
	kNotifID_CreatedReset = 0;
	kNotifID_MovedReset = 1;
	kNotifID_RotatedReset = 2;
	kNotifID_ParameterChangedReset = 3;

	{permissions}
	kObjXPropHasUIOverride = 8;
	kObjXPropAcceptStates = 18;
	
	kDialogIDOffset = 4;
	
	kWidgetButton = 12;
	kButtonID = 1234;
	
VAR
	objEvent, eventData :LONGINT;
	objH, recH, wallH, tempH :HANDLE;
	objName, recName :STRING;
	gDialogID :INTEGER;
	
	gNumEntries :INTEGER;
	gWidth, gHeight, gPadding :REAL;
	
	gVarArr :DYNARRAY[] of INTEGER;
	result :BOOLEAN;
	
PROCEDURE DrawBoxes;
	var
		i,j, rowLength :INTEGER;
		rowStr : dynarray[] of char;
		isEmpty, isLinked :boolean;
	BEGIN
		result := GetCustomObjectInfo(objName, objH, recH, wallH);
		recName := getName(recH);
		if (objH <> nil) then {<--- seems to have an object usually}
		begin
			for i := 0 to gNumEntries-1 do
			BEGIN
			
			{vvv this is always an empty string until dialog is opened vvv}
				rowStr := GetRField(objH, recName, Concat('_',num2str(0,i))); 
				rowLength := Str2Num(rowStr);
				
				for j:= 0 to rowLength-1 do BEGIN
					if rowLength <> 0 then
						Rect(	j*(gWidth+gPadding),		i*(gHeight+gPadding),
								j*(gWidth+gPadding)+gWidth,	i*(gHeight+gPadding)+gHeight);
				end;
			END;	
		end
		else AlertCritical('tried and failed', ''); {<--- never triggers}
	END;
	

PROCEDURE DialogHandler(VAR item:LONGINT; data:LONGINT);
	VAR
		i, val :integer;
	BEGIN
		CASE item OF
			1: BEGIN
				for i:= 0 to gNumEntries-1 do BEGIN
					result := GetEditInteger(gDialogID, (i*2)+kDialogIDOffset+1, val);
					SetRField(objH, recName, Concat('_', num2str(0,i)), Num2Str(0,val));
				end;
			end;
		end;
	END;

PROCEDURE DialogGenerator;
	var
		actionItem 	:LONGINT;
		result 		:BOOLEAN;
		i, tempVal 	:INTEGER;
		tempStr 	:dynarray[] of char;
	begin
		result := GetCustomObjectInfo(objName, objH, recH, wallH);
		
		gDialogID := CreateLayout('Boxes per Row', FALSE, 'OK', 'Cancel');
		for i:= 0 to gNumEntries-1 do BEGIN
			CreateStaticText(gDialogID, (i*2)+kDialogIDOffset, concat('Row ', num2str(0, i+1)), 16);
				
			tempStr := GetRField(objH, recName, Concat('_',num2str(0,i)));

		{vvv creates parameters here for some reason vvv}
			if tempStr = '' then {if parameters don't exist yet}
			begin
				NewField(recName, Concat('_',num2str(0,i)), Num2Str(0,gNumEntries), 1,0);
				tempStr := Num2Str(0,gNumEntries);
			end;
		{^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^}
			CreateEditInteger(gDialogID, (i*2)+kDialogIDOffset+1, Str2Num(tempStr), 16);
		END;
		SetFirstLayoutItem(gDialogID, kDialogIDOffset);
		SetRightItem(gDialogID, kDialogIDOffset, kDialogIDOffset+1, 0, 0);
		for i:= 1 to gNumEntries-1 do BEGIN
			SetBelowItem(gDialogId, (i*2)+kDialogIDOffset-2, (i*2)+kDialogIDOffset, 0, 0);
			SetRightItem(gDialogId, (i*2)+kDialogIDOffset, (i*2)+kDialogIDOffset+1, 0, 0);
		END;
		actionItem := RunLayoutDialog(gDialogID, DialogHandler);
	end;

PROCEDURE init;
	var 
		i :integer;
		tempStr :dynarray[] of char;

	begin
		gNumEntries := PNumber_Of_Rows;
		gWidth := Pw;
		gHeight := Ph;
		gPadding := PPadding;
		
		result := GetCustomObjectInfo(objName, objH, recH, wallH);
		recName := GetName(recH);
				
	end;

PROCEDURE setup;
	var
		i :integer;
		tempStr : dynarray[] of char;
	begin

		result := SetObjPropVs(kObjXPropHasUIOverride, true);
		if result = false then AlertCritical('Error','Failed to override UI');
		result := SetObjPropVs(kObjXPropAcceptStates, true);
		if result = false then AlertCritical('Error','Failed to accept states');
		
		result := vsoInsertAllParams;
		if result = false then AlertCritical('Error', 'There was an issue inserting parameters.');
		if vsoAppendWidget(kWidgetButton, kButtonID, 'Select Columns', 0) = false then AlertCritical('Error', 'button broke');
		
		result := GetCustomObjectInfo(objName, tempH, recH, wallH); {<--- this always fails}
		if result = false then
		begin
			message('setup failed'); 
		end;		
		recName := GetName(recH);
		
		if (objH <> nil) then {<--- this never triggers as GetCustomObjectInfo always fails}
		begin
			for i:=0 to gNumEntries-1 do 
			begin
				if GetRField(objH, recName, Concat('_',num2str(0,i))) = '' then
				begin
					NewField(recName, Concat('_',num2str(0,i)), Num2Str(0,gNumEntries),1,0);
				end;
			end;
		end;
	end;
	
procedure CheckRecalculations;
	var
		outWidgID   : LONGINT;
        outPrmIdx   : INTEGER;
        outOldVal   : STRING;
        i, oldInt 	: INTEGER;
    begin
    	{vvv never runs vvv}
    	if vsoStateGetParamChng(objH, outWidgID, outPrmIdx, outOldVal) then
    	begin
    		if (outPrmIdx = 1) then
    		begin
    			oldInt := Str2Num(outOldVal);
    			if (oldInt < gNumEntries) then 
    				for i:= oldInt-1 to gNumEntries-1 do 
    					NewField(recName, Concat('_',num2str(0,i)),Num2Str(0,gNumEntries),1,0);
    		end;
    	end;
    	if vsoStateGet(objH, kNotifID_CreatedReset) then {<--- never runs}
    	begin
    		setup;	
    	end;
    end;
	
BEGIN
	init;
	BeginGroup;
	vsoGetEventInfo(objEvent, eventData);
	CASE objEvent OF
		kEventID_OnInitProperties: setup;
		kEventID_OnUIButtonHit: DialogGenerator;
		kEventID_ParametricRecalculate:
			begin
				CheckRecalculations;
				DrawBoxes;
				vsoStateClear( objH );
			end;
		kEventID_OnAddState: eventData := vsoStateAddCurrent( objH, eventData );
	END;
	EndGroup;
	ResetObject(objH);
end;

Run(testing);

 

Things I've tried:

Adding the parameter creation code to the draw procedure

    - works in drawing the boxes but the defaults end up being random numbers ("-823648", "2893659")

using CreateCustomObject

    - draws the grid on creation but in the origin and not part of the plugin

using SetCustomObjectPath and setting it to FSActLayer

    - completely fails (im not really sure what the FSActLayer is, I was just following the function reference's example)

 

To my understanding, the object has not actually been created yet in the initProperties event (5) which means I can't access its record to initialise the properties.

This makes no sense and I'm almost certain I'm missing something.

 

Any help would be greatly appreciated!

 

Link to comment

I don't have time to review your code tonight, but one possible issue is that you can not use parameters as variables inside a script. They take a value at the start of the script and return that variable every time it is used during the execution of the script.  You and set the value as many times as you want during the script and the last value will be what is saved when the script ends and what is used when the script runs the next time.

 

The best policy is to read the parameters into a variable at the beginning of the script and write that variable back to the Parameter at the end of the script.

 

HTH

Link to comment

I may in left field here in my understanding but I think I had a similar problem with a plugin that I built to draw pickets in a railing.

I had to separate the drawing of the pickets from the collection of the points I required for the initial placement of the picket plugin.

I used the predefined linear plugin with an additional point parameter as the base plugin to do all the work and used a tool script to gather the initial three definition points and insert them into the instance of the linear plugin's parameters that the tool script places into the document. Manipulation of the linear plugin afterwards is fully handled by the plugin's script. The tool script is only required to initialize the parameter values of the initial placement of the plugin.

 

Link to comment
On 12/1/2022 at 7:29 PM, Gibson431 said:

To my understanding, the object has not actually been created yet in the initProperties event (5) which means I can't access its record to initialise the properties.

This makes no sense and I'm almost certain I'm missing something.

 

Hello @Gibson431,

   Welcome to the club. On your first At Bat, it's pretty much a given that nothing will make sense, and you are missing something, maybe everything. However, this is normal and no reason to turn around. There's only one way to go, and it's INTO the Rabbit Hole. There are many people down here and you're sure to get good advice on your journey.

 

   You are correct, when event 5 (initProperties) is issued, there is no geometry yet, so there is nothing for GetCustomObjectInfo() to return, except FALSE as the result. One thing I'd recommend you do is put an AlrtDialog() at the very front of your code, before the CASE statement and have it report the theEVENT and the theData (or theButton in the following example) for each execution of your code, the two pieces of info that are passed to your code every time it executes. This will show you what events are being sent and in what order, for every thing you do to your object (like place it, move it, rotate it, duplicate it, edit an OIP field, press a Widget Button, etc.) You'll be amazed how many time your script runs in response to every single user action, and which events are issued for each user action. I know, you want to have it draw something first so you can poke it, but trust me, knowing what events are coming, and in what order, will help you get there. For starters, just draw a Rectangle at the origin. Place the Rect() code in Event 3 (kResetEventID), then watch the show as you see the events file in. 

 

   Here is a stripped out shell of an Event Enabled Object I've built in the past. There are things in it that need to be there and I can't explain all of them or what happens if they are not there. Much of the following was handed to me in dribs and drabs and I use it as a stepping stone to create new objects. Defining which procedures go into which events may require you to write back (a lot), but that is encouraged. Some are obvious, some are not. Trial and error are your friends.

 

PROCEDURE YourCodeName;
CONST
	kResetEventID = 3;
	kObjXPropPreference = 4;
	kObjOnInitXProperties = 5;
	kObjXPropHasUIOverride = 8;
	kObjXHasCustomWidgetVisibilities = 12;
	kObjXPropAcceptStates = 18;
	kObjOnObjectUIButtonHit = 35;
	kObjOnWidgetPrep = 41;
	kObjOnAddState = 44;
	
	kWidgetButton = 12;					{ 12 = Button WIdget }
	buttonID_1 = 1001;					{ user-definable index }
	buttonID_2 = 1002;					{ user-definable index }
	buttonID_3 = 1003;					{ user-definable index }
VAR
	H, PIOHand, recHand, wallHand :Handle;
	result, IsNew, WidgetChanged :Boolean;
	WidgetIndex :Integer; 
	theEvent, theButton, DoesNothing, WidgetID, WidgetMenuPos :Longint;
	PIOName, OldSel :String;
	P0 :Vector;

BEGIN		{ MAIN LOOP }
	result := GetCustomObjectInfo(PIOName, PIOHand, recHand, wallHand);
	IsNew := IsNewCustomObject(PIOName);
	PIOrot := GetSymRot(PIOHand);
	GetSymLoc(PIOHand, P0.x, P0.y);				{ PIO insertion point }
	vsoGetEventInfo(theEvent, theButton);			{ VERY IMPORTANT }
	
{ comment out the following line when not needed. }
AlrtDialog(concat('Top of Loop:  event=  ', theEvent, '    data=  ', theButton, chr(13), 'hPIO≠nil  ', PIOHand<>nil, '    Is new  ', IsNew, chr(13), 'Result= ', result));


	case theEvent of

		kObjOnInitXProperties: begin	{ 5 }						{ User has single-clicked the object's icon. }
			result := SetObjPropVS(kObjXPropPreference, TRUE);			{ 4, has custom Pref Dialog, OR don't open Object Properties dialog.  }
			result := SetObjPropVS(kObjXPropHasUIOverride, TRUE);			{ 8, This tells VW to let the object decide what goes onto the OIP. }
			result := SetObjPropVS(kObjXHasCustomWidgetVisibilities, TRUE);		{ 12, enables Event 41 (kObjOnWidgetPrep). }
			result := SetObjPropVS(kObjXPropAcceptStates, TRUE);			{ 18, enable eventing for this plug-in. }
			
			result := vsoInsertAllParams;								{ Manually add the "normal" parameters... }
			result := vsoInsertWidget(9, kWidgetButton, 1003, 'Button 3 text', DoesNothing);	{ add button 3 }
			result := vsoInsertWidget(4, kWidgetButton, 1002, 'Button 2 text', DoesNothing);	{ add button 2 }
			result := vsoInsertWidget(0, kWidgetButton, 1001, 'Button 1 text', DoesNothing);	{ add button 1 }
			
			SetPrefInt(590, 1);									{ varParametricEnableStateEventing, kParametricStateEvent_ResetStateEvent }
		end;		{ 5 }


		kResetEventID: begin		{ 3 }	{ Object reset – redraw everything }
			WidgetChanged := vsoStateGetParamChng(PIOHand, WidgetID, WidgetIndex, OldSel);
			case WidgetIndex of
				{ respond to changes in the OIP }
			end;		{ case }

			{ draw PIO geometry }
			DrawSomething;

			vsoStateClear(PIOHand);		{ necessary }
		end;		{ 3 }


		kObjOnObjectUIButtonHit: begin	{ 35 }	{ User has clicked a button in the Object Info palette. }
			case theButton of
				buttonID_1: begin end;
				buttonID_2: begin end;
				buttonID_3: begin end;
			end;		{ case }
			ResetObject(PIOHand);
		end;		{ 35 }


		kObjOnWidgetPrep: begin		{ 41 }
			vsoSetEventResult(-8);		{ kObjectEventHandled }
		end;		{ 41 }


		kObjOnAddState: begin		{ 44 }
			theButton := vsoStateAddCurrent(PIOHand, theButton);
		end;		{ 44 }

	end;		{ case }

END;
Run(YourCodeName);

 

   One hint, PIOs pretty much start up not accepting any events. You enable the events you want to see with the SetObjPropVS() calls. I've commented what some of them do in the code above. When you get further in, you can play around with turning them ON and OFF to see what happens. There are a lot more events that are being ignored than the ones I've enabled, but many are specialized and you'll never need them. These will get you started.

 

Welcome to the Warren, 🐰

Raymond

 

Link to comment
On 12/3/2022 at 4:46 PM, MullinRJ said:

   You are correct, when event 5 (initProperties) is issued, there is no geometry yet, so there is nothing for GetCustomObjectInfo() to return, except FALSE as the result. One thing I'd recommend you do is put an AlrtDialog() at the very front of your code, before the CASE statement and have it report the theEVENT and the theData (or theButton in the following example) for each execution of your code, the two pieces of info that are passed to your code every time it executes. This will show you what events are being sent and in what order, for every thing you do to your object (like place it, move it, rotate it, duplicate it, edit an OIP field, press a Widget Button, etc.) You'll be amazed how many time your script runs in response to every single user action, and which events are issued for each user action. I know, you want to have it draw something first so you can poke it, but trust me, knowing what events are coming, and in what order, will help you get there. For starters, just draw a Rectangle at the origin. Place the Rect() code in Event 3 (kResetEventID), then watch the show as you see the events file in. 

This is really useful, thank you so much! I will not lose hope!

Link to comment
5 hours ago, SamIWas said:

Is it literally just a grid of squares?  Can you enter your info via the OIP, or does it have to be by dialog?   Because if it's just a grid of X x Y squares of A x B size with C padding, and the info can entered in the OIP, this seems overly complicated.

Unfortunately this code is just an analog of the task I need to do. The grid of squares if just the way I decided to visualise the program. All it boils down to is being able to create a variable number of parameters based on input, and having them populate on object creation as well as be individually editable by the user in the OIP (or dialog in this case).

Link to comment
  • Vectorworks, Inc Employee

If I'm reading the code correctly you are trying to add fields to the parametric object record in Vectorscript which is not possible.

If you are trying to build the OIP and the underlying parametric record dynamically in Vectorscript that's not possible.
You can dynamically build  a variable number controls in the dialog but the you will need to need to store the data differently because you can't add record fields to the parametric record via Vectorscript.

You have 3 options.
If you know the maximum number of fields create them all and show/hide them as needed. (This is the easiest solution but limiting.)
You can create and store the data in a standard record format, that allows you to add new fields as necessary.
Store the data in a delimited list in a hidden parameter of the parametric object. You will need to insert and extract the data as needed.

(You may also be able to add the controls to the OIP but you will still need to manage the data for the controls with your code, Vectorworks will not do it automatically for you.)
 

  • 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...