Jump to content

Python Dialog Box


Recommended Posts

Hi all, new to Vectorworks, but I've been using Python for a couple years and have used it a lot in TouchDesigner. I want to create a custom dialog box to collect user input parameters and then create a new symbol, but I'm lost where to even begin. I keep trying to run various dialog functions and then I get an error saying that they don't exist. example:

import vs
vs.BeginDialog(0, 1, 10, 10, 0, 0)
vs.EndDialog()

 

I get an error saying that vs has no attribute BeginDialog. Then I tried  vs.DrawDialog(), thinking that would be a better place to start, and I get the same error. All of the class functions work fine, as well as the predefined dialogs, what am I doing wrong here?

 

 

Also, in general, are there any more extensive resources for the Python API? The VS Function Reference wiki page is kind of awful.

Link to comment

Hi @sully8391 ,

   The "vs.BeginDialog()" and "vs.EndDialog()" commands are part of the Classic Dialog set which were discontinued in VW 2010. They continued to run for several years after that, but the shift was toward the Modern Dialog commands introduced in VW 9. They are the currently supported dialog calls.

 

   Consult the Developer Wiki at http://developer.vectorworks.net/index.php/VS:Function_Reference or the HTML Script Reference that ships with the application in the VWHelp folder @ "VWHelp/Script Reference/ScriptFunctionReference.html". Start with "CreateLayout()" and peruse that section. There are several examples in that section. If you're looking at the HTML Reference,, search for "Example" when you're in the "Modern Dialog" area.

 

   Simple dialogs are pretty easy to layout by hand. More complicated dialogs might require the use of the Dialog Builder. Good luck and write back.

 

Raymond

 

PS - Please create a signature with a basic description of your computer and VW version. It will help us answer your questions a little faster and possibly allow us to offer advice that is pertinent to your setup.

  • Like 1
Link to comment

I see the note there now that they've been deprecated. I still find the VS Function Reference lacking. I've been at it hours, and I can't even get a simple dialog box with a button. The examples don't make much sense to me. I'm seeing functions defined inside other function definitions, and functions that seem to do nothing but define global variables. The comments are also sparse, so I'm just having a lot of trouble figuring out how these elements are supposed to work together. 

Link to comment

You picked the wrong spot to start with PythonScript and Vectorworks. There is nothing simple about a custom dialog box.

 

If you only need a single or couple of inputs, try and do it with the Predefined dialog box (es) so you can get the rest of the code to work. Then you can go back and consider making a custom dialog.

 

For most of my (few) applications that use a custom dialog box, the code to draw and handle the dialogs is far more than the rest of the code.

  • Like 1
Link to comment

@sully8391 ,

Although Pat's advice is pretty much spot on, let me lessen the gravity of it a little. If you're tenacious, patient, and inquisitive, you can probably have your dialog up and running within a day or two. Once you get the first one done all the hurdles come down a bit. And as with all other disciplines: practice makes perfect, and failure is mandatory.

 

Modern Dialogs are constructed in three parts. The first part can be a procedure, or inline code, that contains all of the statements that define the dialog, while the second part is a procedure containing code to run the dialog (the event loop). The last part is typically one or two lines in the main program that call the dialog code.


Here is a short example in Pascal (sorry, I think faster this way.) Translating it shouldn't be too hard. The CASE statement in the Event Loop can be replaced with an "if / elif" tree.

PROCEDURE DialogExample;
{ This is a relatively simple example of a Modern Dialog that converts temperatures. }
{ It has 6 fields, 3 static text labels, and 3 Edit Text fields. }
{ It also as a basic event loop to control the dialog when it is running. }
{ Every key press and every click generates an event which is handled by the event loop. }
{ Events can be handled or ignored as you see fit. }
{ 03 Jan 2020 - Raymond J Mullin - Use or modify to your wildest whims. }

VAR
	dlogID, dlogResult :Longint;

	function BuildDialog :Longint;
	{ This dialog returns the Dialog ID and has 2 parts. }
	{ 1) Code that creates the dialog elements. }
	{ 2) Code that arranges the elements in the dialog, either below or to the right of the previous element. }
	{ This function could also contain two more parts... }
	{ 3) Code that controls the alignment of the elements to each other. This is optional. }
	{ 4) Code that defines Help Strings when the cursor hovers over dialog elements. This is also optional. }
	Var
		dlogID :Longint;
	Begin
		{ This is the most common way to start. }
		dlogID := CreateLayout('Temperature', False, 'OK', '');	{ No HELP text, OK button and no Cancel button }

		{ create 1st element at 4 or higher, it's arbitrary. I usually start at 10 for static text and 20 for the edit fields }
		CreateStaticText(dlogID, 10, 'Fahrenheit (°F)', 13);	{ Name says it all - it's static (usually) }
		CreateStaticText(dlogID, 11, 'Celsius (°C)', 13);	{ the last number is its width in characters (approximately))... }
		CreateStaticText(dlogID, 12, 'Kelvin (°K)', 13);	{   ...average characters, not the skinny ones }
		CreateEditReal(dlogID, 20, 1, 32, 16);			{ a field you can type in }
		CreateEditReal(dlogID, 21, 1, 0, 16);			{ another field you can type in }
		CreateEditReal(dlogID, 22, 1, 273.15, 16);		{ a third field you can type in, or write to }

		{ The next section arranges the dialog elements either right or down }
		SetFirstLayoutItem(dlogID, 10);				{ everything else is anchored to this item }
		SetBelowItem(dlogID, 10, 11, 0, 0);			{ item 11 goes below item 10 }
		SetBelowItem(dlogID, 11, 12, 0, 0);			{ item 12 goes below item 11 }
		SetRightItem(dlogID, 10, 20, 0, 0);			{ item 20 goes to right of item 10 }
		SetRightItem(dlogID, 11, 21, 0, 0);			{ item 21 goes to right of item 11 }
		SetRightItem(dlogID, 12, 22, 0, 0);			{ item 22 goes to right of item 12 }

		BuildDialog := dlogID;					{ this is your "return" value in Python }
	End;		{ BuildDialog }
	
	
	procedure DialogEventLoop(var item :Longint; data :Longint);	{ this procedure is always defined this way }
	{ This is the Event Loop. For every event (keystroke or click) this procedure is executed }
	Var
		Fahr, Cel, Kel :Real;
	Begin
		{ use this next line for debug to see what events are called and when. Comment it out when done. }
		{	AlrtDialog(concat('item= ', item, '   data= ', data));	}
		
		{ if you want something to happen when an event occurs, put the item # (aka, the event #) in the case statement }
		case item of
			SetupDialogC : begin
				{ initialize all dialog values here }
				{ this event only gets called once at the beginning of dialog execution }
			end;		{ SetupDialogC }
			
			1: begin		{ OK button was pressed }
				SysBeep;	{ BEEP for joy - it worked }
			end;		{ 1 }
				
			2: begin		{ code for CANCEL button was pressed, if you had one (but you don''t) }
			end;		{ 2 }
			
			20: begin
				if GetEditReal(dlogID, 20, 1, Fahr) then begin
					Cel := (Fahr-32) * 5/9;
					SetEditReal(dlogID, 21, 1, Cel);
					SetEditReal(dlogID, 22, 1, Cel+273.15);
				end;		{ if }
			end;		{ 20 }
			
			21: begin
				if GetEditReal(dlogID, 21, 1, Cel) then begin
					Fahr := Cel * 9/5 + 32;
					SetEditReal(dlogID, 20, 1, Fahr);
					SetEditReal(dlogID, 22, 1, Cel+273.15);
				end;		{ if }
			end;		{ 21 }
				
			22: begin
				if GetEditReal(dlogID, 22, 1, Kel) then begin
					Cel := Kel - 273.15;
					SetEditReal(dlogID, 21, 1, Cel);
					SetEditReal(dlogID, 20, 1, Cel*9/5+32);
				end;		{ if }
			end;		{ 22 }
		end;		{ case }
		
		{ the dialog exits when (item == 1) or (item == 2) at the end of this procedure. }
		{ this procedure will "return item" in Python }
	End;		{ DialogEventLoop }

BEGIN		{ main program }
	{ This line builds the dialog and returns its ID. }
	dlogID := BuildDialog;
	
	{ VerifyLayout() checks the dialog, if it passes then it runs the dialog }
	{ in the next statement with the Event Loop specified. }
	if VerifyLayout(dlogID) then
		dlogResult := RunLayoutDialog(dlogID, DialogEventLoop);
		
	{ Check the "dlogResult", 1 = OK; 2 = Cancel. Proceed accordingly. }
END;		{ main program }
Run(DialogExample);

 

Keep the Function Reference close. Even with all its shortcomings it is still an invaluable aid. The next best reference source is this forum. Write back when you have more questions and @Pat Stanford will most likely beat me to a response. 😉

 

Raymond

 

 

Edited by MullinRJ
  • Like 1
Link to comment
  • 4 weeks later...

Hello

 

I created for my pyhton script two types of custom dialogs a simple one and a version I can define the custom dialog by an string (multiple strings passed to the same Dialog-ID). I put both dialogs in a function placed in a lib, so I have them "handy" for all scripts:

 

simple dialog:

 

def dialog_input_simple(vDialog_Titel = '', vDialog_Text = '', vDialog_Text_help = '', vDialog_Text_input = ''):

	cCancelButton = 2
	cOKButton = 1
	cSetupDialog = 12255


	def setup(vDialog_Titel, vDialog_Text, vDialog_Text_help, vDialog_Text_input):
	
		vDialog_ID = vs.CreateLayout(vDialog_Titel,1,'ausführen','abbrechen')
		vs.CreateStaticText(vDialog_ID,100,vDialog_Text,-1)
		vs.CreateEditText(vDialog_ID,300,vDialog_Text_input,40)

		vs.SetFirstLayoutItem(vDialog_ID, 100)
		vs.SetBelowItem (vDialog_ID,100,300,0,0)

		vs.SetHelpText(vDialog_ID, 300,vDialog_Text_help)

		return (vDialog_ID)
		
		
	def control(vDialog_Item, vDialog_Data):
	
		# do not write any code here, as it is executed on every event cycle, even after the ok-event
		nonlocal vDialog_Text_input

		if vDialog_Item == cSetupDialog:
			vDialog_Text_input = ''
		elif vDialog_Item == cCancelButton:
			pass # pass statement is a null operation
		elif vDialog_Item == cOKButton:
			vDialog_Text_input = vs.GetItemText(vDialog_ID, 300)
			# vestB.messageA(vObject_ID)
			
		return (vDialog_Item)
	
	vDialog_ID = setup(vDialog_Titel, vDialog_Text, vDialog_Text_help, vDialog_Text_input)
	vDialog_Result = vs.RunLayoutDialog(vDialog_ID, control)

	return (vDialog_Result, vDialog_Text_input)

 

variable custom dialog:

 

def dialog_input_multiple(vDialogParameters = ""):

	cCancelButton = 2
	cOKButton = 1
	cSetupDialog = 12255
	
	vDialog_Result = cCancelButton

	def setup(vDialogParameters_list):
	
		vDialog_Titel = vDialogParameters_list['title']
		vDialog_Text = vDialogParameters_list['head']
	
		vDialog_Items_search = 'item'
		vDialog_Items_List = [vKey for vKey,vValue in vDialogParameters_list.items() if (vKey.lower()).startswith(vDialog_Items_search)]
	
		vDialog_ID = vs.CreateLayout(vDialog_Titel,1,'ausführen','abbrechen')
		
		vDialog_Item_ID_start = 1000
		vDialog_Item_ID = vDialog_Item_ID_start
		
		vs.CreateStaticText(vDialog_ID,vDialog_Item_ID,vDialog_Text,-1)
		vs.SetFirstLayoutItem(vDialog_ID, vDialog_Item_ID)
		
		vDialog_Items_List.sort() # this sorts only on items key and not on the ID, future sorting should be on ID
		# messageA(vDialog_Items_List)
		
		vDialog_Results = list()
		
		for vDialog_Item_key in vDialog_Items_List:
			
			vDialog_Item_values = vest.base.vw.lists.keyvalueList_ToList(vest.base.vw.lists.text_ToList_embraced(vDialogParameters_list[vDialog_Item_key]))
			
			vDialog_Item_ID_previous = vDialog_Item_ID
			vDialog_Item_ID_org = vDialog_Item_values['ID']
			vDialog_Item_ID = int(vDialog_Item_ID_org)+(2*vDialog_Item_ID_start)
			vDialog_Item_name = vDialog_Item_values['name']
			vDialog_Item_label = vDialog_Item_values['label']
			vDialog_Item_input = vDialog_Item_values['input']
			vDialog_Item_help = vDialog_Item_values['help']
			
			# messageA("vDialog_Item_ID_previous | vDialog_Item_ID:" + str(vDialog_Item_ID_previous) + " | " + str(vDialog_Item_ID))
			vs.CreateEditText(vDialog_ID,vDialog_Item_ID,vDialog_Item_input,40)
			vs.SetBelowItem (vDialog_ID,vDialog_Item_ID_previous,vDialog_Item_ID,0,0)
			vs.SetHelpText(vDialog_ID, vDialog_Item_ID,vDialog_Item_help)
			
			vDialog_Results.append({'dialogID': vDialog_Item_ID, 'ID': vDialog_Item_ID_org, 'name': vDialog_Item_name, 'input': vDialog_Item_input})

		return (vDialog_ID, vDialog_Results)
		
		
	def control(vDialog_Item, vDialog_Data):
	
		# do not write any code here, as it is executed on every event cycle, even after the ok-event
		nonlocal vDialog_Results

		if vDialog_Item == cSetupDialog:
			pass # pass statement is a null operation
		elif vDialog_Item == cCancelButton:
			pass # pass statement is a null operation
		elif vDialog_Item == cOKButton:
			for vDialog_Results_item in vDialog_Results:
				vDialog_Item_ID = vDialog_Results_item['dialogID']
				vDialog_Results_item['input'] = vs.GetItemText(vDialog_ID, vDialog_Item_ID)
			
		return (vDialog_Item)
	
	
	vDialogParameters_list = vest.base.vw.lists.keyvalue_ToList(vDialogParameters, ';', '+=')

	if len(vDialogParameters_list)>0:
		
		vDialog_ID, vDialog_Results = setup(vDialogParameters_list)
		vDialog_Result = vs.RunLayoutDialog(vDialog_ID, control)

	return (vDialog_Result, vDialog_Results)

# END		:	vest.base.vw.base.dialog_input_multiple

 

the variable dialog can by called by these parameters:

 

# dialog_input_multiple(vDialogParameters = "") # "title+=Titel;head+=Head;item1+=[ID=100] [name=field 100] [label=label 100] [input=input 100] [help=help 100] [type=text];"

 

or as example:

 

	vDialog_Parameters = ''
	vDialog_Parameters += 'title+=Table Export;'
	vDialog_Parameters += 'head+=Entry of DB-name and list of all DB-field-name;'
	vDialog_Parameters += 'item1+=[ID=100] [name=db_name] [label=DB name] [input='+ vDB_Name +'] [help=please enter the DB-name] [type=text];'
	vDialog_Parameters += 'item2+=[ID=110] [name=db_field_list] [label=Feld Namen] [input='+ vDB_Field_Name_TextList +'] [help=please enter all DB-field names as a list : field1, field2 ... oder !ALL! for all DB fields] [type=text];'

	vDialog_Result, vDialog_Results = vestB.dialog_input_multiple(vDialog_Parameters)

 

and here the functions called inside of the dialog_input_multiple(vDialogParameters) function to decode the input string vDialogParameters:

 

def keyvalue_ToList(vValues_text, vValues_Entity_Split = ';', vValues_Pair_Split = '='): # 'key1=value1;key2=value2;key3=value3'

		vEntity_List = text_ToList(vValues_text, vValues_Entity_Split)
		vValues_list = list()
		vValues_list = dict(vEntity.split(vValues_Pair_Split) for vEntity in vEntity_List) 
	
		return (vValues_list)



def text_ToList(vValues_text, vValues_Entity_Split = ',' ):
	import re

	vValues_list = list()
	vValues_list = re.split(r"" + vValues_Entity_Split + "\s*", vValues_text)
	vValues_list = list(filter(None,vValues_list)) # removes void/empty items
	
	return (vValues_list)
    
    
    
def text_ToList_embraced(vValues_text, vValues_Entity_SplitStart = '', vValues_Entity_SplitEnd = ''):
	import re

	vValues_list = list()
	if vValues_Entity_SplitStart != '' and vValues_Entity_SplitEnd != '':
		vPattern = re.escape(vValues_Entity_SplitStart) +  "[^" + re.escape(vValues_Entity_SplitEnd) + "]*" + re.escape(vValues_Entity_SplitEnd) + "|\S+"
		vValues_list = re.findall(vPattern, vValues_text)
		vPatternReplace = "\A" + re.escape(vValues_Entity_SplitStart) + "|" + re.escape(vValues_Entity_SplitEnd )+ "\Z"
		vValues_list = [re.sub(vPatternReplace, '', item) for item in vValues_list] # to strip leading and trailing embracing strings
	else:
		vValues_list = re.findall('\[[^\]]*\]|\([^\)]*\)|\"[^\"]*\"|\S+',vValues_text)
		vValues_list = [re.sub('\A\[|\]\Z|\A\(|\)\Z|\A\"|\"\Z', '', item) for item in vValues_list]
		
	vValues_list = list(filter(None,vValues_list)) # removes void/empty items

	
	return (vValues_list)

 

best regards,

 

relume

 

 

 

Edited by relume
added secondary functions
  • 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...