addCommand: How to address variables of instance?

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
Markymark
Posts: 228
Joined: Sun Nov 03, 2019 4:54 pm

addCommand: How to address variables of instance?

Post by Markymark »

Define a command:

Code: Select all

class MyCommand:
 
     def __init__(self):
        self.flag_something = True
        
Register command:

Code: Select all

FreeCADGui.addCommand('My_Command', MyCommand())
As far as I understand, there should now be an instance available. How can I address "flag_something"? Tried different approaches but to no avail.

Thanks again in advance. m.
vocx
Veteran
Posts: 5197
Joined: Thu Oct 18, 2018 9:18 pm

Re: addCommand: How to address variables of instance?

Post by vocx »

Markymark wrote: Mon May 25, 2020 8:38 am ...
As far as I understand, there should now be an instance available. How can I address "flag_something"? Tried different approaches but to no avail...
What do you want to do?

That variable only exists when you actually run that command.

Code: Select all

FreeCADGui.runCommand('My_Command')
It is inside the class of course. Or do you mean you want to have a persistent "global" variable for all instances?

See how the commands of the Draft Workbench are defined, in Mod/Draft/draftguitools/. Many of them depend on the Modifier or Creator classes, whose parent, DraftTool, defines many variables that are available for use by the children classes, say, Line (gui_lines.py).
Always add the important information to your posts if you need help. Also see Tutorials and Video tutorials.
To support the documentation effort, and code development, your donation is appreciated: liberapay.com/FreeCAD.
User avatar
Markymark
Posts: 228
Joined: Sun Nov 03, 2019 4:54 pm

Re: addCommand: How to address variables of instance?

Post by Markymark »

That variable only exists when you actually run that command.
I really have to wrap my head arround scopes once again and refresh this topic ... I was mislead by myself.
Or do you mean you want to have a persistent "global" variable for all instances?
I might be completly wrong, but shouldn't there by just one instance of that command?
See how the commands of the Draft Workbench are defined,
OK, will do. Thanks. m.


What do you want to do?
Sorry, forgot to answer. Tried to have a variable that "belongs" to that command for status information. This should be "globaly" available. But I see my lack of understanding. Will have to do some reading.
wmayer
Founder
Posts: 20319
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: addCommand: How to address variables of instance?

Post by wmayer »

Markymark wrote: Mon May 25, 2020 8:38 am As far as I understand, there should now be an instance available. How can I address "flag_something"? Tried different approaches but to no avail.
This is a very simple example how you can do it:

Code: Select all

import __main__

class MyCommand:
  def __init__(self):
    print ("Create command")
    self.flag_something = True
    __main__.link_from_command = self
  def __del__(self):
    print ("Delete command")
  def Activated(self):
    self.flag_something = False
    print ("Activate command")
  def GetResources(self):
   return {}

FreeCADGui.addCommand('My_Command', MyCommand())
__main__.link_from_command.flag_something
FreeCADGui.runCommand('My_Command')
__main__.link_from_command.flag_something
The module __main__ keeps a dict of the global variables.
I might be completly wrong, but shouldn't there by just one instance of that command?
Yes, FreeCAD internally has a std::map where a key is unique. If you repeat FreeCADGui.addCommand('My_Command', MyCommand()) a few times only the last added command is used.

However, the previous instances still live in memory but can't be addressed anymore. And that's why the __del__() function of the class is not invoked.
vocx
Veteran
Posts: 5197
Joined: Thu Oct 18, 2018 9:18 pm

Re: addCommand: How to address variables of instance?

Post by vocx »

Markymark wrote: Mon May 25, 2020 12:43 pm ...
I might be completly wrong, but shouldn't there by just one instance of that command?
I don't know the internals of FreeCAD to be able to answer that exactly, but my empirical experience tells me every time you run a command, you essentially instantiate a new command "object", just like you would call the class in code.

Code: Select all

one = MyCommand() # it will call __init__ and then Activate
two = MyCommand() # again
three = MyCommand() # again
Maybe I'm mistaken, and Werner can tell you more.
...Tried to have a variable that "belongs" to that command for status information. This should be "globaly" available. But I see my lack of understanding. Will have to do some reading.
The common way to solve this is by using parameters. See the Parameter editor. Essentially, you define a parameter which can be a string, an integer, a float, a boolean, etc., and you can read and write to it.

Say that you call the command, you read from the parameter, or use its default value. During execution of the command you change the value of the parameter, and finish. Next time you call the same command, you read the same parameter, which then would hold the state of the previous execution.

Code: Select all

class MyCommand:
    def Activate(self):
        param_group = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Something")
        value = param_group.GetInt("Line_thickness", 4)  # Default is 4 if the parameter does not exist, that is, on first run
        # ... do something
        param_group.SetInt("Line_thickness", 3)  # Writes the parameter value, it creates the parameter on first run
There are other ways, for example, using the Meta attribute of the document.
Always add the important information to your posts if you need help. Also see Tutorials and Video tutorials.
To support the documentation effort, and code development, your donation is appreciated: liberapay.com/FreeCAD.
User avatar
Markymark
Posts: 228
Joined: Sun Nov 03, 2019 4:54 pm

Re: addCommand: How to address variables of instance?

Post by Markymark »

This is a very simple example how you can do it:
The module __main__ keeps a dict of the global variables.
Great, got it and learned something. This is quite hackish I think, but it was only intended for a quick fix anyway.

Thanks. Mark
wmayer
Founder
Posts: 20319
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: addCommand: How to address variables of instance?

Post by wmayer »

vocx wrote: Mon May 25, 2020 5:04 pm I don't know the internals of FreeCAD to be able to answer that exactly, but my empirical experience tells me every time you run a command, you essentially instantiate a new command "object", just like you would call the class in code.
No, FreeCAD keeps a reference to the Python command object passed with FreeCADGui.addCommand. Internally a C++ wrapper is instantiated that is derived from the C++ class Gui::Command and this wrapper invokes the appropriate functions of the Python object.
Markymark wrote: Mon May 25, 2020 5:22 pm Great, got it and learned something. This is quite hackish I think, but it was only intended for a quick fix anyway.
Instead of registering a command with

Code: Select all

FreeCADGui.addCommand('My_Command', MyCommand())
you can do it this way

Code: Select all

link_to_command = MyCommand()
FreeCADGui.addCommand('My_Command', link_to_command)
This is the cleaner and more direct way.
vocx
Veteran
Posts: 5197
Joined: Thu Oct 18, 2018 9:18 pm

Re: addCommand: How to address variables of instance?

Post by vocx »

wmayer wrote: Mon May 25, 2020 8:48 pm No, FreeCAD keeps a reference to the Python command object passed with FreeCADGui.addCommand. Internally a C++ wrapper is instantiated that is derived from the C++ class Gui::Command and this wrapper invokes the appropriate functions of the Python object...
Yes, but does the wrapper call __init__ again, or only Activated? I feel it still calls the entire constructor when you run the command.
Always add the important information to your posts if you need help. Also see Tutorials and Video tutorials.
To support the documentation effort, and code development, your donation is appreciated: liberapay.com/FreeCAD.
wmayer
Founder
Posts: 20319
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: addCommand: How to address variables of instance?

Post by wmayer »

vocx wrote: Mon May 25, 2020 11:17 pm
wmayer wrote: Mon May 25, 2020 8:48 pm No, FreeCAD keeps a reference to the Python command object passed with FreeCADGui.addCommand. Internally a C++ wrapper is instantiated that is derived from the C++ class Gui::Command and this wrapper invokes the appropriate functions of the Python object...
Yes, but does the wrapper call __init__ again, or only Activated? I feel it still calls the entire constructor when you run the command.
No, it doesn't call __init__, why should it? Here is the implementation of PythonCommand that acts as a wrapper of the Python command object:
https://github.com/FreeCAD/FreeCAD/blob ... .cpp#L1267

From my example above it would print the message "Create command" each time if the Python command were created inside PythonCommand::Activated(), i.e. when calling FreeCADGui.runCommand('My_Command').
Although never used so far but a Python command class could be implemented to have arguments in its __init__ method. But this would cause problems because PythonCommand::Activated() wouldn't know which arguments this should be.
User avatar
Markymark
Posts: 228
Joined: Sun Nov 03, 2019 4:54 pm

Re: addCommand: How to address variables of instance?

Post by Markymark »

wmayer wrote: Mon May 25, 2020 8:48 pm you can do it this way

Code: Select all

link_to_command = MyCommand()
FreeCADGui.addCommand('My_Command', link_to_command)
This is the cleaner and more direct way.
OK, thanks. But it is good to see it can be done with the __main__-module, as well. Doesn't seem to be very prevalent knowledge.
vocx wrote: Mon May 25, 2020 5:04 pm The common way to solve this is by using parameters. See the Parameter editor. Essentially, you define a parameter which can be a string, an integer, a float, a boolean, etc., and you can read and write to it.
OK, good to know.
Post Reply