Skip to content

Interactor PythonPart

In an interactor PythonPart, you implement the entire workflow. This is done by building a class with certain methods. When your PythonPart is run, the framework will call these methods, depending on the action taken by the user. Your interactor will then handle these actions. So the user acts, your PythonPart reacts - an interaction is established.

Because with an interactor Python you can precisely react to almost any action taken by the user in Allplan UI (you will handle literally each mouse movement), 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. Creation of the element can be done, unlike in Standard PythonPart, in any moment of the workflow and more than one time. It is also possible to modify existing elements including the deletion.

With an interactor PythonPart, you can truly create an entire functionality, that does nor yet exists in Allplan.

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:

<Script>
    ...
    <Interactor>True</Interactor>
    ...
</Script>

Now, implement following two functions in the PY file:

create_interactor

create_interactor(
    coord_input: 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],
) -> Interactor

Parameters:

  • coord_input (CoordinateInput) –

    coordinate input

  • pyp_path (str) –

    path of the pyp file

  • global_str_table_service (StringTableService) –

    global string table service for default strings

  • build_ele_list (list[BuildingElement]) –

    list with the building elements containing parameter properties

  • build_ele_composite (BuildingElementComposite) –

    building element composite

  • control_props_list (list[BuildingElementControlProperties]) –

    control properties list

  • modify_uuid_list (list[str]) –

    UUIDs of the existing elements in the modification mode

Returns:

  • Interactor

    Created interactor object

Source code in src\PythonPartsScriptTemplates\InteractorPythonPart.py
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]) -> "Interactor":
    """Function for the interactor creation, called when PythonPart is initialized.
    When called, the PythonPart framework performs the following steps:

    - reads the parameters and their values from the xxx.pyp file and stores them in the buld_ele_list
    - if tag `ReadLastInput` is set to True: read the parameter values from the last input,
        (stored in ...\\Usr\\_user_name_\\tmp\\_python_part_name.pyv) and assign them to the parameters
        in build_ele_list
    - if starting an input by Match from the context menu or double right click: read the parameter
        values from the attribute @611@ of the matched PythonPart and assign them to the parameters
        in build_ele_list
    - if in modification mode: read the parameter values from the attribute @611@ of the selected
        PythonPart and assign them to the parameters in build_ele_list

    Args:
        coord_input:               coordinate input
        pyp_path:                  path of the pyp file
        global_str_table_service:  global string table service for default strings
        build_ele_list:            list with the building elements containing parameter properties
        build_ele_composite:       building element composite
        control_props_list:        control properties list
        modify_uuid_list:          UUIDs of the existing elements in the modification mode

    Returns:
        Created interactor object
    """

    return Interactor(coord_input, pyp_path, global_str_table_service, build_ele_list,
                      build_ele_composite, control_props_list, modify_uuid_list)

Example

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)
  1. You implement the MyInteractor class. The name is exemplary: name it as you want it. Scroll down below to learn, how to implement it.

Warning

In some older examples you may still encounter the following implementation:

def create_interactor(coord_input:              AllplanIFWInput.CoordinateInput,
                      pyp_path:                 str,
                      global_str_table_service: StringTableService) -> object:

    return MyInteractor(...)

This signature is soft-deprecated. We recommend using the new one, with 7 arguments.

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:

from BaseInteractor import BaseInteractor

class MyInteractor(BaseInteractor):
    ...

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:

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

  2. 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 return True
    Left mouse button is clicked inside the viewport process_mouse_msg calling IsMouseMove will return False
    Mouse leaves the viewport on_mouse_leave
    Input in the property palette is done modify_element_property confirming the input with Enter triggers the event; name and new value is passed to the function as argument
    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
  3. 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)!
    
    1. When hitting Esc while the first point input is active, the PythonPart should shut down completely...
    2. Hitting Esc during the second point input should only switch to the first point input.
    3. The False value is returned here to communicate to the PythonPart framework, not to terminate the running PythonPart.
  4. 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.

    def on_cancel_by_menu_function(self):
        palette_service.close_palette() #(1)!
    
    1. 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{{action}}:::action
        A --> m
    end

    START{{PythonPart 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" --> Z{{PythonPart termination}}:::action
    on_cancel_by_menu_function --> Z

    classDef meth stroke:#f00
    classDef action stroke:#0f0

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:

Returns:

Source code in src\PythonPartsScriptTemplates\InteractorPythonPart.py
def create_preview(build_ele: BuildingElement,
                   doc: AllplanElementAdapter.DocumentAdapter) -> CreateElementResult:
    """ Create the library preview

    Args:
        build_ele: building element with the parameter properties
        doc:       document of the Allplan drawing files

    Returns:
        created element result
    """
    return CreateElementResult(elements=[...])

  1. Dialog line is the input toolbar, by default shown at the bottom left corner of Allplan UI, where the user can input XYZ coordinates.

    Dialog line