sully8391 1 Posted February 1, 2020 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. Quote Share this post Link to post
MullinRJ 203 Posted February 2, 2020 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. 1 Quote Share this post Link to post
sully8391 1 Posted February 2, 2020 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. Quote Share this post Link to post
Pat Stanford 1,445 Posted February 3, 2020 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. 1 Quote Share this post Link to post
MullinRJ 203 Posted February 3, 2020 (edited) @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 February 3, 2020 by MullinRJ 1 Quote Share this post Link to post
Pat Stanford 1,445 Posted February 3, 2020 I may be quick, but in many cases I am simplistic. I can help get you around the simple issues, but Raymond is the best about digging into the deep heart of the matter. 1 Quote Share this post Link to post
relume 1 Posted March 1, 2020 (edited) 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 March 2, 2020 by relume added secondary functions Quote Share this post Link to post