Interactor PythonPart
In an interactor PythonPart, you implement the entire workflow. You do it by building a class, that handles the user interaction accordingly: an interactor class. The class has certain methods, that gets called by the PythonParts framework in specific situations, e.g. when user clicks in the viewport.
Because the interactor class can precisely handle almost any action taken by the user in ALLPLAN UI, it allows you to build freely any workflow. E.g. you can include steps like element selection or point input and this in any order you want. Transaction with the data base (creating, deleting or changing an element), unlike in Standard PythonPart, can take place in any moment of the workflow and more than one time.
Implementation
First steps
First, tell the PythonParts framework, that the script implementation is done as an interactor
by setting the <Interactor>
tag to True in the PYP file, in the <Script>
section:
Now, implement following two functions in the PY file:
-
This function is called at the very beginning to check, whether the PythonPart can be started in the current ALLPLAN version. Implementation is exactly the same, as in the Standard PythonPart
-
This function is called directly after the version check. It should only construct and return your Interactor object.
create_interactor
create_interactor(interactor_data: BaseInteractorData) -> Interactor
Parameters:
-
interactor_data
(BaseInteractorData
) –object containing the data for the interactor creation
Returns:
-
Interactor
–Created interactor object
Source code in src\PythonPartsScriptTemplates\InteractorPythonPart.py
Example
def create_interactor(interactor_data: BaseInteractorData) -> MyInteractor:
return MyInteractor(interactor_data) #(1)!
- The name
MyInteractor
is exemplary: it's your class, so name it as you want it. The only requirement is: it needs to inherit from BaseInteractor Scroll below you can learn more.
Warning
In some older examples you may encounter a constructor with 3 arguments like:
def create_interactor(coord_input: AllplanIFWInput.CoordinateInput,
pyp_path: str,
global_str_table_service: StringTableService) -> object:
return MyInteractor(...)
... or a constructor with 7 arguments, like:
def create_interactor(coord_input: AllplanIFWInput.CoordinateInput,
pyp_path: str,
global_str_table_service: StringTableService,
build_ele_list: list[BuildingElement],
build_ele_composite: BuildingElementComposite,
control_props_list: list[BuildingElementControlProperties],
modify_uuid_list: list[str]):
return MyInteractor(coord_input, pyp_path, global_str_table_service, build_ele_list, #(1)!
build_ele_composite, control_props_list, modify_uuid_list)
There is nothing wrong in using them, but the newest and recommended signature
is the one with one argument of type BaseInteractorData
Interactor class
The interactor object is basically an event handler: its methods handle events triggered by actions done in ALLPLAN UI. In other words: PythonParts framework calls these methods during the runtime of the PythonPart, depending on what action is taken by the user.
Some of these methods are mandatory to implement, some optional. To ensure that your interactor class fulfill the contract properly, use the BaseInteractor as base class:
The BaseInteractor class contains all the methods, that framework will look for. The mandatory ones are implemented as abstract methods, and the optional ones as stub methods. You should therefore always override these methods in your implementation.
Events
What methods of the interactor are called, depends on the action done by the user in ALLPLAN UI. The general script flow is as follows:
-
The PythonPart is started. The interactor object is constructed, i.e. the
__init__()
is called. Therefore, it is a good practice to implement here these actions, that must be done only once, at the very beginning. For example:def __init__(self, interactor_data: BaseInteractorData): # read only these objects from the interactor_data, that you'll need later self.build_ele = cast(BuildingElement, interactor_data.build_ele_list[0]) self.coord_input = interactor_data.coord_input self.modification_element_list = interactor_data.modify_uuid_list ... # showing the palette is a typical action done only once at the beginning self.palette_service = BuildingElementPaletteService(interactor_data.build_ele_list, interactor_data.build_ele_composite, self.build_ele.script_name, interactor_data.control_props_list, self.build_ele.pyp_file_name) self.palette_service.show_palette(self.build_ele.pyp_file_name)
-
During the runtime of the PythonPart, actions taken by the user triggers method calls. See table below for some common actions and the method, that gets called.
Action Called method Remarks Mouse is moved inside the viewport process_mouse_msg calling IsMouseMove()
will returnTrue
Left mouse button is clicked inside the viewport process_mouse_msg calling IsMouseMove()
will returnFalse
Mouse leaves the viewport on_mouse_leave Input in the property palette is done modify_element_property only confirming the input with Enter triggers the event Input in the dialog line1 is done on_preview_draw any change triggers the event, pressing Enter is not necessary Enter is hit during the input in the dialog line on_value_input_control_enter A button in the property palette is pressed on_control_event ID of the pressed button is passed to the function as argument Property palette tab is changed set_active_palette_page_index Restore basic settings button is hit in the property palette reset_param_values -
When the user hits Esc, the on_cancel_function is called. Depending on the returned value the PythonPart is either terminated (
True
) or continue running (False
).Tip
If your PythonPart consists of multiple input steps, you can handle the event of hitting the Esc as a backward step to the previous input. When the user hit Esc so many times, that the first input step is reached, hitting Esc one more time should terminate the PythonPart. This is a common practice for many native ALLPLAN function and we recommend to align with it.
Here is how the implementation can look like for a two-step workflow, where the user must input first point in the first step, and another point in the second step:
def on_cancel_function(self): if self.first_point_input: #(1)! palette_service.close_palette() return True self.first_point_input = True #(2)! coord_input.InitFirstPointInput(AllplanIFW.InputStringConvert("From point")) return False #(3)!
- When hitting Esc while the first point input is active, the PythonPart should shut down completely...
- Hitting Esc during the second point input should only switch to the first point input.
- The False value is returned here to communicate to the PythonPart framework, not to terminate the running PythonPart.
-
When the user starts another ALLPLAN function during the runtime of the PythonPart, the on_cancel_by_menu_function is called. Then, the PythonPart is terminated. Always.
- After calling this function and completing the procedures in it, PythonPart framework will always shut down the PythonPart. A return value is not needed.
Info
When on_cancel_by_menu_function is not implemented, the framework will call the
on_cancel_function
over and over, until the returned value is True. This multiple calls may cause the palette to blink. If your implementation of on_cancel_function always returns True, this won't be a problem. Otherwise, we recommend implementing the on_cancel_by_menu_function
The following flow chart shows the workflow described above. The user action is shown symbolically as action
and the method called by it as interactor_method()
, as showing all of them on the graph would make it hard
to read.
graph TB
subgraph runtime ["PythonPart runtime"]
m["interactor_method()"]:::meth --> A{{"user action"}}:::action
A --> m
end
START((start)):::action --> init["__init__()"]:::meth
init --> runtime
runtime -- "esc pressed" --> on_cancel_function["on_cancel_function()"]:::meth
runtime -- "another function started" --> on_cancel_by_menu_function["on_cancel_by_menu_function()"]:::meth
on_cancel_function -- "False" --> runtime
on_cancel_function -- "True" --> END((end)):::action
on_cancel_by_menu_function --> END
classDef meth stroke:#f00
classDef action stroke:#0f0
Example on GitHub
To see, what event is triggered in which situation, run the example Events ( PYP | PY) in ALLPLAN. Then take any action in GUI, like move the mouse or input something in the property palette. The triggered event will be printed in the ALLPLAN trace window.
Optional functions
To create elements, which should be displayed in the ALLPLAN library as a preview, implement the function create_preview into your script. See the chapter Library preview to learn more.
create_preview
create_preview(
build_ele: BuildingElement, doc: DocumentAdapter
) -> CreateElementResult
Parameters:
-
build_ele
(BuildingElement
) –building element with the parameter properties
-
doc
(DocumentAdapter
) –document of the Allplan drawing files
Returns:
-
CreateElementResult
–created element result
Source code in src\PythonPartsScriptTemplates\InteractorPythonPart.py
-
Dialog line is the input toolbar, by default shown at the bottom left corner of ALLPLAN UI, where the user can input XYZ coordinates.