Jump to content

Working with vs.GetPt() & Event based Plug In Objects


Recommended Posts

Hi All

 

So I'm trying to add functionality into an already existing Event based PIO that allows the user to click a button in the OIP and choose a Lighting fixture on the drawing, and get some information from it.

 

The bit I'm struggling with is finding a way to use vs.GetPt() (or any similar user interactive command) within a Python Event based plug in object.

 

The structure of the Plug In is pretty standard, the main outline of which is below.

 

def makeOIP():
    global objectHand , BackgrdColourDict, ForeGrdColDict, position_note_rec_name
    ok, objectName, objectHand, recordHand, wallHand = vs.GetCustomObjectInfo()
    theEvent, theEventData = vs.vsoGetEventInfo()  # Gets Object Event info
    
    if theEvent == tb.Constants.kObjOnInitXProperties:  # If the event is the initialisation of the object
      ok = vs.SetObjPropVS(tb.Constants.kObjXPropPreference, True)  # Allows for creation of custom OIP
      ok = vs.SetObjPropVS(tb.Constants.kObjXPropHasUIOverride, True)  # Custom OIP Overrides standard UI
      ok = vs.SetObjPropVS(tb.Constants.kObjXPropPreference,True)  # Enables preferences menu for PIO
      ok = vs.SetObjPropVS(tb.Constants.kObjXHasCustomWidgetVisibilities,True)



      vs.vsoInsertAllParams()  # Inserts all Parameters floato OIP

      result, scaleWidID = vs.vsoPrmName2WidgetID('','Scale')
      thisDoesNothing = 0
      result = vs.vsoAppendWidget(tb.widgetTypes.WidgetButton, link_pos_button_id,'Get Position Info', thisDoesNothing)
        
    if theEvent == tb.Constants.kObjOnWidgetPrep:
       WidgetPrep()  # Sets Info within the OIP
    
    if theEvent == tb.Constants.kObjXPropPreference:
        defaultVals = {} # Creates blank Dict for default vals
        MakePropertiesDialog()
    
    if theEvent == tb.Constants.kObjOnObjectUIButtonHit:  # If the event is a button being pressed
        if theEventData == link_pos_button_id:  # If the Button Pressed is Link Position Button
            link_to_hp()  # Runs 
    if theEvent == tb.Constants.kResetEventID:  # If Object is reset
        init()  # Creates the Object

 

 

When the button is pressed it calls a function, link_to_hp()

 

def link_to_hp():
    vs.SetObjPropVS(tb.Constants.kObjXHasCustomWidgetVisibilities, False)  # Sets widget custom visibility to false
    try:
       vs.GetPt(PickPointCallback)
    except Exception as e:
        vs.AlrtDialog(str(e))
        h, p = 0,0

    Fix_H = vs.Rpstr_GetValueStr(tb.ValueStrings.FixtureHandle_TempValString,False)  # Stores the Fixture handle as a Vectorworks Value String
    vs.Message(str(Fix_H))

    vs.SetObjPropVS(tb.Constants.kObjXHasCustomWidgetVisibilities, True)  # Sets widget custom visibility to True

def PickPointCallback(pt):
    vs.Message('X: ', pt[0], 'Y: ', pt[1])
    Fix_H = vs.vstGetPickObject(pt[0],pt[1])
    # Stores the fixture handle as a VWX Value string for retrieving later after GetPt callback has been run
    vs.Rpstr_SetValueStr(tb.ValueStrings.FixtureHandle_TempValString,Fix_H)

    vs.SetObjPropVS(tb.Constants.kObjXHasCustomWidgetVisibilities, True)  # Sets the Custom Widget visibulites back to True

 

 

The main issue I'm having is that whenever I press the button, the GetPt cross hairs appear, I'm able to select a point, but then Vectorworks crashes, with no error message. I understand the main issue is that the Python function GetPt will not block execution, so I need to use the callback function. However I am still getting the same results.

 

From a fair amount of searching of the forums, I have found several suggested solutions such as @Paolo suggestion in this thread to disable custom widget visibilities whilst vs.GetPt is running, however sadly this does not seem to have any affect.

 

I've seen suggestions that there cannot be any functions inbetween the GetPt call, purely as a test I've tried adding a vs.GetPt() at the end of the of the file, with some very dodgy logic to allow it only to run after the UIButton has been hit.

E.g:

makeOIP()
if runGetPt:
    vs.GetPt(PickPointCallback)

Still no improvement, still crashes with no error.

 

I've tried completely emptying the PickPointCallback, in case it was something inside there, but still no change.

 

Does anyone have any suggestions as to how to achieve this kind of functionality within a Python Script? Surely people have managed to find a way around this? @Vlado @JBenghiat @twk 

 

Thank you in advance!

Edited by tbexon
Link to comment

Also tried to solve that issue. But without a big success. You can run the complete python as a callback of GetPt. But Nested callbacks are not possible. So just one click can be done.

My Workflow is to create a main script as a Vectorscript. I collect the clicks and user interaction and then run the python script inside this script. Also it is possible to run several python scripts inside the pascal wrapper. So far no better solution I can see.

It gets two clicks in the drawing and draw a preview rectangle. Then save the result in the value reprository, where the python script after that can load the data. It you want to pause somewhere your python script and make a user interaction this workflow is not possible. 

Example for the Pasca-Script that calls the python script:

 


PROCEDURE Reshape;
VAR
	h1, h2, h3 :HANDLE;
	scriptName, values2python : STRING;
	py, res: BOOLEAN;
	s: DYNARRAY OF CHAR;
	pt1, pt2 : POINT;
	pt, pt_tl, pt_br : POINT;
	plan_rotation : REAL;
	
	
 FUNCTION TempToolCallback(action, msg1, msg2 : LONGINT) : LONGINT;
    
    BEGIN
         TempToolCallback := 0;
         CASE action OF
             3: BEGIN {kOnToolDoSetupEventID}
		             vstSetHelpString ( 'definiere rechteckigen Umformbereich auf' );
             END;

             103 : BEGIN {kToolDrawEventID}
                 vstGetCurrPt2D( pt.x, pt.y );
                 
                 pt_tl.x := Min(pt.x, pt1.x);
                 pt_tl.y := Max(pt.y, pt1.y);  
				 pt_br.x := Max(pt.x, pt1.x);
                 pt_br.y := Min(pt.y, pt1.y);
				 
                 
                 vstDrawCoordLine( pt_tl.x, pt_tl.y, pt_br.x, pt_br.y );
                 vstDrawCoordRect( pt_tl.x, pt_tl.y, pt_br.x, pt_br.y );

             END;
         END;
    END;



BEGIN
	plan_rotation := GetPrefReal(93);
	
	IF (plan_rotation > 0) 
	THEN 
	BEGIN
	AlrtDialog('Dieser Befehl funktioniert nicht in der Planrotation');
	END
	
	ELSE
	BEGIN

	GetPt(pt1.x, pt1.y);
	RunTempTool( TempToolCallback, FALSE );
	{using $ as Delimiter, because in Germany comma is reservated for decimal seperator}
	Rpstr_SetValueStr('values2python', Concat('(',Num2Str(6, pt_tl.x),'$',Num2Str(6, pt_tl.y),')','$','(',Num2Str(6, pt_br.x),'$',Num2Str(6, pt_br.y),')'));
	
	res := GetScriptResource('Subscript', s, py);

     PythonExecute(s);
	END;
END;
RUN(Reshape);


 

  • Like 1
Link to comment

It looks for interaction with the VW API it would need a very different kind if integration of the python language or additional APIs which are able to communicate with the python script. Which maybe needed a very big effort or would not pay out the effort. I think the reason it not, that python can't pause.

If i draw an object and then get after that some informations from that object, somehow the object was created in Vectorworks (not drawn, but minimum defined). So The python script here waits for the Object created by the VW binary and so i think it could be possible the python script also can await the user interaction. So I guess it user interaction may be is not technically limited because of python itself. 
 

Link to comment

Thanks Dom & Pat, I was worried this may be the case.

 

Thank you for your suggested solution Dom, unfortunately because I'm only looking to get user input when a button is clicked not on script initialisation, the only way I could see to implement this is to rewrite the base code for building the PIO in Vectorscript, and then call multiple python scripts at appropriate points. Given that this Plug-In is over 2000 Lines of Python, I just don't see the work required to implement this as a reasonable option for the functionality gain.  

 

I'd be curious to see what is stopping this functionality from being implemented within the Python side of Vector script, Python itself as a programming language definitely has tools to support this.  From a personal perspective it certainly would be a big benefit to some of my tools!

Link to comment

So thanks to the wonderful @VladoI have managed to get this working! There is a bug with developer mode that was causing the crash. Disabling developer mode, so that the script didn't run twice each time solved the crashing issue.

 

Below is an explanation of my process, and what I understood is happening based on Vlados Explanation to me.

 

Working with a Event Enabled Plug In Object, use the below template.

 

When the button in the OIP is clicked, the ButtonClick() function is called. Within this function we call the vs.GetPt() command, passing a function as an arguement (in this case PickPointCallback). 

 

The way I understand what's happening is: When you run GetPt, it effectively restarts the script, clearing out all your code, and JUST runs whatever code is within the PickPointCallback function. Hence why any variables you may try and call or save won't affect anything outside of the function. To get around this, within the PickPointCallback we can save whatever values we wish to the Value Repository, which will temporarily save them into the documents session.

 

I am saving 2 Values. Firstly a string containing the Co ordinates the user selected. The second is a bool indicating that the GetPt has been called but not handled. The reason for this is that once the GetPt call has been made the PIO won't automatically reset. We need to manually send it a reset event using vs.ResetObject(objectHandle). The reason i'm using the 'newFixtureBool' is because otherwise the main Init code won't know whether it's running after the UIButton has been pressed, or from some other Reset Event. There are other approaches, you don't necessarily need to use a Bool to track whether the button has been hit or not, it really depends on your specific project.

 

Hopefully this is of help to someone, if anything is unclear, please do just ask, and I will do my best to help! Once again massive thanks to Vlado for figuring this out, and sending me on the right path! 

 

def makeOIP():
    global objectHand , BackgrdColourDict, ForeGrdColDict, position_note_rec_name
    ok, objectName, objectHand, recordHand, wallHand = vs.GetCustomObjectInfo()
    theEvent, theEventData = vs.vsoGetEventInfo()  # Gets Object Event info
    
    if theEvent == tb.Constants.kObjOnInitXProperties:  # If the event is the initialisation of the object
      ok = vs.SetObjPropVS(tb.Constants.kObjXPropPreference, True)  # Allows for creation of custom OIP
      ok = vs.SetObjPropVS(tb.Constants.kObjXPropHasUIOverride, True)  # Custom OIP Overrides standard UI
      ok = vs.SetObjPropVS(tb.Constants.kObjXPropPreference,True)  # Enables preferences menu for PIO
      ok = vs.SetObjPropVS(tb.Constants.kObjXHasCustomWidgetVisibilities,True)

      vs.vsoInsertAllParams()  # Inserts all Parameters floato OIP

      result, scaleWidID = vs.vsoPrmName2WidgetID('','Scale')
      thisDoesNothing = 0
      result = vs.vsoAppendWidget(tb.widgetTypes.WidgetButton, link_pos_button_id,'Get Position Info', thisDoesNothing)
        
    if theEvent == tb.Constants.kObjOnWidgetPrep:
       WidgetPrep()  # Sets Info within the OIP
    
    if theEvent == tb.Constants.kObjXPropPreference:
        defaultVals = {} # Creates blank Dict for default vals
        MakePropertiesDialog()
    
    if theEvent == tb.Constants.kObjOnObjectUIButtonHit:  # If the event is a button being pressed
        if theEventData == link_pos_button_id:  # If the Button Pressed is Link Position Button
            ButtonClickFunction()  # Runs the GetPt Command
            vs.ResetObject(objectHand)  # Resets the Plug In object
    if theEvent == tb.Constants.kResetEventID:  # If Object is reset
        init()  # Creates the Object
        
makeOIP()

 

def ButtonClickFunction():
    """
    Prompts the user to select a point on the drawing, and then calls the PickPointCallback function with the
    co ordinates for the user specified location as an arguement 
    :return: 
    """

    try:
       vs.GetPt(PickPointCallback)  # Prompts user to select a point on the drawing
    except Exception as e:
        vs.AlrtDialog(str(e))
        h, p = 0,0

 

def PickPointCallback(pt):
    """
    Callback function used in GetPt call to allow user to select
    :param pt: XY Co ords user selects
    :return:
    """

    # Stores the fixture position as a VWX Value string for retrieving later after GetPt callback has been run
    vs.Rpstr_SetValueStr('tb__PositionLoc',str(pt))
    vs.Rpstr_SetValueBool('tb__newFixtureBool',True)

 

def init():
    """
    Runs whenever object reset event is called
    :return:
    """

    NewFixH_bool = vs.Rpstr_GetValueBool('tb__newFixtureBool',False)
    Fix_H = vs.Rpstr_GetValueStr('tb__PositionLoc', False)  # mGets the saved fixture handle
    if NewFixH_bool:  # If the GetPt call has been made, but not handled 
        SetPositionData()  # Gets the saved GetPt data and do whatever you would like with it

 

  • Like 2
Link to comment
  • 2 weeks later...
  • Vectorworks, Inc Employee
On 4/23/2023 at 8:03 AM, tbexon said:

There is a bug with developer mode that was causing the crash.

FYI, I found the bug and it will be fixed in Vectorworks 2024. Then the 'developer mode' option would not cause crash in these python-execution-suspend situations.

  • Like 3
Link to comment

@tbexon, you're a genius, I kept crashing previously, regardless of the developer mode on or off. The only thing that was missing from my code, was the reset object_hand call.

 

Also, what is happening here:

    if theEvent == tb.Constants.kObjXPropPreference:
        defaultVals = {} # Creates blank Dict for default vals
        MakePropertiesDialog()

 

don't think I've ever use the kObjXPropPreference constant in my plugins

Link to comment

I'll be honest I've just gone back over my template, and I've no idea why I'm using kObjXPropPreference...

 

It's been a couple of years since I made that template. But certainly in terms of functionality it appears to work the same as kObjOnInitXProperties. (though I'm sure there is a difference) maybe @JBenghiat can shed some light).

 

But basically within that call i'm just building the preferences dialog, and setting default values

Edited by tbexon
  • Like 1
Link to comment

This simple script (outside of using plugins) crashes vw. Can someone else confirm? @Vlado, what am I doing wrong here?

import vs

h = vs.FSActLayer()

def callback(pt1, pt2):
    vs.AlrtDialog(f'pt1: {pt1}, pt2: {pt2}')

vs.GetRect(callback)

 

Link to comment

Found the issue.

I develop in the PyCharm IDE. When I'm doing rapid testing, I just run the Import/Run Script Command in Vectorworks and point to the py file. Which effectively runs the python script in Vectorworks. 99% of the time it works, except for these interactive functions I've just found out.

 

However I can still develop in PyCharm, and rapid test, but I need to create a script resource in Vectorworks, and have the script hold the import functions call pointing to that py file/function from here. Then it works.

Will submit a ticket

Link to comment
  • Vectorworks, Inc Employee
3 hours ago, twk said:

runs the python script in Vectorworks. 99% of the time it works, except for these interactive functions I've just found out.

 

Yep, I reproduced it too. Same problem but in the file execution, and thus not affected by the debug-mode option.

Will be fixed in Vectorworks 2024.

Thank you @twk!

  • Like 1
Link to comment
On 5/3/2023 at 2:44 AM, twk said:

@tbexon, you're a genius, I kept crashing previously, regardless of the developer mode on or off. The only thing that was missing from my code, was the reset object_hand call.

 

Also, what is happening here:

    if theEvent == tb.Constants.kObjXPropPreference:
        defaultVals = {} # Creates blank Dict for default vals
        MakePropertiesDialog()

 

don't think I've ever use the kObjXPropPreference constant in my plugins

This is the event raised when VW requests a preferences/settings dialog for the PIO. By default, this is basically just a floating version of ObjInfo, but if you set

 

ok = vs.SetObjPropVS(tb.Constants.kObjXPropPreference, True) # Allows for creation of custom OIP

 

You can handle the event and show a custom dialog. The Door, Window, and Data Tag insertion tools are all an example of this.

  • Like 2
Link to comment
  • 2 weeks later...

@twk could  you post an example of what you mean

On 5/5/2023 at 8:10 AM, twk said:

Found the issue.

I develop in the PyCharm IDE. When I'm doing rapid testing, I just run the Import/Run Script Command in Vectorworks and point to the py file. Which effectively runs the python script in Vectorworks. 99% of the time it works, except for these interactive functions I've just found out.

 

However I can still develop in PyCharm, and rapid test, but I need to create a script resource in Vectorworks, and have the script hold the import functions call pointing to that py file/function from here. Then it works.

Will submit a ticket

 

I'm wondering if the issue I'm having here is related: 

 

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