Skip to content

Selection

Abstract

All the implementation examples and code snippets shown in this article can be used only in an Interactor PythonPart.

Single selection

To perform an element selection, where the user is allowed to click in the viewport and select only one object, introduce following steps:

  1. Initialize the input. This is done at the beginning of the workflow, i.e. must be implemented in the constructor method of your interactor class
  2. Optionally apply a filter. See below, how to set up one.
  3. Perform an element search. This must be done with every mouse movement, i.e. must be implemented in the process_mouse_msg method of the interactor class.
  4. Get the element. This action should also be be implemented in the process_mouse_msg method, but only if the mouse was clicked.

  5. Now you can introduce any actions e.g., modify the elements.

    Info

    If the next action should be a selection of another element, reinitialize the selection by calling the InitNextElementInput. This will ensure, that the selection is possible only in the same document, in which the first selection was done.

  6. After the actions are done, reinitialize the input by jumping back to the first step. Alternatively, terminate the PythonPart by calling CancelInput

    Warning

    If the PythonPart should not terminate after the actions are done, it is important to always reinitialize the input at this point! Not doing so, may lead to an unexpected behavior or even a crash.

Example

Here is a simple implementation of a workflow consisting of just one element selection. After an element is selected, some action is introduced and the selection is restarted. No filter is applied for the selection.

flowchart TB
    InitInput["initialize
               selection"]
    SetFilter[apply filter]
    ElementSearch["search for
                   element"]
    IsClick{Mouse\nclicked?}
    ElementFound{"element
                  found?"}
    GetElement[get the element]
    Action[do something...]
    ReinitInput["reinitialize
                 selection"]
    End[return]

    style Action stroke-dasharray: 5 5

    subgraph __init__
        direction LR
        InitInput --> SetFilter
    end


    subgraph process_mouse_msg
        direction LR
        ElementSearch --> ElementFound
        ElementFound -->|No| End
        ElementFound -->|Yes| IsClick
        IsClick -->|Moved| End

        subgraph do_something
            direction TB
            GetElement --> Action
            Action --> ReinitInput
        end

        IsClick -->|Clicked| do_something
        do_something --> End
    end

    __init__ -- "mouse message\nsent" --> process_mouse_msg
class MyInteractor(BaseInteractor):

    def __init__(self, coord_input, ...):

        self.coord_input = coord_input

        prompt_msg = AllplanIFW.InputStringConvert("Select the element")
        self.coord_input.InitFirstElementInput(prompt_msg)
        self.coord_input.SetElementFilter(sel_filter)
    ...
    def process_mouse_msg(self, mouse_msg, pnt, msg_info):
        element_found = self.coord_input.SelectElement(mouse_msg, pnt, msg_info,
                                                       True, True, True)

        if self.coord_input.IsMouseMove(mouse_msg) or not element_found: #(1)!
            return True

        selected_element = self.coord_input.GetSelectedElement()

        ####### here introduce some action #########

        self.coord_input.InitFirstElementInput(
            AllplanIFW.InputStringConvert("Select the element")) #(2)!

        return True
  1. By calling IsMouseMove you can obtain, whether a mouse click was performed or the mouse was just moved.
  2. Remember to reinitialize the input after the action is completed!

Start selection

An element selection can be started by calling the InitFirstElementInput like this:

prompt_msg = AllplanIFW.InputStringConvert("Select the element")
self.coord_input.InitFirstElementInput(prompt_msg)
self.coord_input.SetElementFilter(sel_filter) #(1)!
  1. At this point you can optionally apply a selection filter. See below, how to set up one

Prompt message in dialog line

This will result in a prompt message appearing in the dialog line (by default located in the bottom left corner of the Allplan UI). In this way you can communicate to the user, what your script is expecting from him.

An element selection can also be started by calling the InitFirstElementValueInput like this:

prompt_msg = AllplanIFW.InputStringConvert("Select the element; input value:")

input_control_type = AllplanIFW.eValueInputControlType.eANGLE_COMBOBOX #(1)!
input_control      = AllplanIFW.ValueInputControlData(input_control_type,
                                                      bSetFocus     = False,
                                                      bDisableCoord = False)

self.coord_input.InitFirstElementValueInput(prompt_msg, input_control)
self.coord_input.SetElementFilter(sel_filter) #(2)!
  1. To see, what input controls are possible and what is their behavior, refer to the documentation of eValueInputControlType and have a look on the example SingleSelection
  2. At this point you can optionally apply a selection filter. See below, how to set up one

Input control in dialog line

This will result in not only a prompt message, but also an input control appearing in the dialog line. This is a useful feature, when your PythonPart is so simple, that it requires only one or two parameter. In this case it is possible to do the workflow without the property palette.

To read the entered value call the method GetInputControlValue

Tip

Any change in this input control triggers the on_preview_draw event. Pressing Enter triggers the on_value_input_control_enter event.

Performing an element search does two things:

  • gives you bool value, whether an element was found or not
  • highlights the element (optionally)

You can either search for an element or for an element geometry

Element search

Element search is triggered by calling the SelectElement method.

ele_found = self.coord_input.SelectElement(mouse_msg, pnt, msg_info,
                                           True, False, False)

In this case, the element is found, when the user hovers over the entire element. When found, element info box appears at the cross hair and (optionally) the entire element is highlighted.

This is the standard way of how you should search for elements.

Geometry search

Geometry search is triggered by calling the SelectGeometryElement method.

ele_found = self.coord_input.SelectGeometryElement(mouse_msg, pnt, msg_info, False)

In this case, the element is found, when the user hovers over the edges of the element, but not the inside of the element. The difference is clearly visible in case of architecture elements in the ground view. This kind of search is particularly useful, when you are interested in the geometry of the element (e.g. you want to get a vertex, edge or face of an element), but not in the element itself.

By default, only the selected geometry is highlighted. Optionally, the entire element can be highlighted.

Getting the element

Getting the element is done by calling the GetSelectedElement method. This actually reads the data from the found element and, for better performance, should be be done only when needed e.g., when the mouse was clicked and an element was found.

def process_mouse_msg(self, ...)
    ...
    if not self.coord_input.IsMouseMove(mouse_msg) and ele_found:
        selected_element = self.coord_input.GetSelectedElement()

As a result, you will get a BaseElementAdapter - a type of object, which we describe in detail here.

Tip

If you are interested only in the geometry of the element, you can call the GetSelectedGeometryElement method instead. This is an equivalent to getting the adapter in the first step and geometry from the adapter with GetGeometry in the second step.

Info

The result if the element selection is always the same, regardless of the method used for the element search

Example

To try out the different element search methods or see, what impact on the element selection do the options of CoordinateInput have, run the example SingleSelection located in:

  • …\etc\Examples\PythonParts\ModelObjectExamples\SelectionExamples\SingleSelection.pyp
  • …\etc\PythonPartsExampleScripts\ModelObjectExamples\SelectionExamples\SingleSelection.py

Multiple selection

Multiple selection

If your PythonPart requires a selection of multiple elements and you want to enable the user to select them by specifying a region rather than clicking individual elements, use the InputFunctionStarter class. It provides methods to start and terminate the default element selection functionality of Allplan, where the user can draw a rectangle in the viewport, and all elements inside it will be selected. He can also use the bracket or fence functionality.

Info

After calling StartElementSelect the selection function is started and it the overloads the process_mouse_msg event! It means that as long as the selection is not completed by the user, this event is not called!

Failure

Make sure, that you don't start a selection function, when one is already running, as this will lead to a crash!

To restart an already running selection function, call the RemoveFunction first and then start a new one. Bare in mind, that calling RemoveFunction when no selection function is running also leads to a crash!

Warning

During the selection, the user can define his own filter using Allplan filtering functionality. If he does so, it will overwrite the ElementSelectFilterSetting passed to the StartElementSelect method! Therefore, you must take into account a case, in which the final list of selected elements contains elements that your filter would exclude!

Example

Here is a simple implementation of a workflow consisting of a selection of multiple elements. After at least one element is selected, an action is introduced and the selection is then restarted.

flowchart LR
    Init([__init__])
    StartSelection["start\nselection"]
    ListEmpty{List\nempty?}
    GetElements[get\nelement\nlist]
    Action[do something...]
    style Action stroke-dasharray: 5 5

    Init --> StartSelection
    StartSelection -->|successful\nselection| GetElements
    GetElements --> ListEmpty
    ListEmpty -- No --> Action
    ListEmpty -- Yes --> StartSelection
    Action --> StartSelection
class MyInteractor(BaseInteractor):

    def __init__(self, coord_input, ...):

        self.post_element_selection = AllplanIFW.PostElementSelection()       #(1)!
        self.sel_filter             = AllplanIFW.ElementSelectFilterSetting()
        self.document               = coord_input.GetInputViewDocument()
        self.start_selection()
    ...
    def process_mouse_msg(self, mouse_msg, pnt, msg_info):

        elements = self.post_element_selection.GetSelectedElements(self.document)

        if len(elements) == 0:  #(2)!
            self.start_selection()
            return True

        ####### here introduce some action #########

        self.start_selection()
        return True

    def start_selection(self):
        AllplanIFW.InputFunctionStarter.StartElementSelect("Select elements",
                                                           self.sel_filter, #(3)!
                                                           self.post_element_selection,
                                                           markSelectedElements = True)
  1. This is a special object, where the result of the selection will be saved after the selection is completed
  2. It is possible, that a selection was successful, but no elements were selected. This is the case, when e.g. the user has drawn a selection rectangle, but no objects were in it. In such case, it makes sense to restart the selection right away.
  3. Here is where you can apply a selection filter. In this example, the filter is empty. See below to learn how to set up a filter

    Bare in mind, that when the user uses Allplan filtering functionality, this filter will be overwritten!


For the entire implementation, refer to the example MultipleSelection, located in:

  • …\etc\Examples\PythonParts\ModelObjectExamples\SelectionExamples\MultipleSelection.pyp
  • …\etc\PythonPartsExampleScripts\ModelObjectExamples\SelectionExamples\MultipleSelection.py

Face selection

Face selection

To search for a face of a polyhedron, modify the 4th step from this list. First, getting the element must be performed with every mouse movement in order to correctly highlight the face. Directly after the element adapter is retrieved with GetSelectedElement method, call the SelectPolyhedronFace.

selected_element = self.coord_input.GetSelectedElement()

if polyhedron_ele.IsNull():
    return True

is_selected, face_polygon, intersect_result = \
    AllplanBaseEle.FaceSelectService.SelectPolyhedronFace(
        selected_element,
        pnt,
        True, #(1)!
        self.coord_input.GetViewWorldProjection(),
        self.coord_input.GetInputViewDocument(),
        False) #(2)!
  1. This option will highlight the selected face
  2. If you want to allow the face selection in a UVS, set this to True. If you want to allow the selection only in a UVS, use a dedicated method SelectPolyhedronFaceInUVS

As a result, you will get three elements:

  • a boolean value indicating that a face was successfully selected
  • a Polygon3D representing the selected face
  • an IntersectRayPolyhedron containing information such as normal vector of the selected face or the point on the face that was clicked.

Example

See the complete implementation in the example PolyhedronFaceSelection located in:

  • …\etc\Examples\PythonParts\ModelObjectExamples\SelectionExamples\PolyhedronFaceSelection.pyp
  • …\etc\PythonPartsExampleScripts\ModelObjectExamples\SelectionExamples\PolyhedronFaceSelection.py

Tip

For selection of a wall face, we recommend to use a dedicated method SelectWallFace. It is similar to one for selecting polyhedron faces, but snoops only to walls. It also guarantees that the face is always an outer face of the wall. Refer to the example WallFaceSelection to see the full implementation.

Setting up a filter

The selection filter is represented by the ElementSelectFilterSetting class. It contains settings regarding what elements should be valid for selection. These settings have their representation, here's a short overview:

Setting description Represented by
In which DF to search for elements for (active, passive, both) eDocumentSnoopType
In which layer to search for elements for (active, passive, both) eLayerSnoopType
Objects valid for selection SelectionQuery
classDiagram
    direction BT


class ElementSelectFilterSetting{
    __init__(SelectionQuery, eDocumentSnoopType, eLayerSnoopType)
    SetDocumentSelectType(eDocumentSnoopType)
    SetLayerSelectType(eLayerSnoopType)
}
class eDocumentSnoopType{
    <<Enumeration>>
    eSnoopActiveDocuments
    eSnoopPassiveDocuments
    eSnoopAllDocuments
}
class eLayerSnoopType{
    <<Enumeration>>
    eSnoopActiveLayers
    eSnoopPassiveLayers
    eSnoopAllLayers
}
class SelectionQuery{
    __init__(list[QueryTypeID | CustomFilter] query)
}

class QueryTypeID{
    __init__(GUID)
}

class CustomFilter{
    __call__(BaseElementAdapter)
}

class GUID
GUID --* "1" QueryTypeID
QueryTypeID --* "0..*" SelectionQuery
CustomFilter --* "0..*" SelectionQuery
SelectionQuery --* "1" ElementSelectFilterSetting
eDocumentSnoopType --* "1" ElementSelectFilterSetting
eLayerSnoopType --* "1" ElementSelectFilterSetting

Selection query

SelectionQuery is the object responsible for checking individual elements whether they can be selected or not. It can be a collection of element types valid for selection (column, 2D-line, filling, etc...) or callable classes returning a bool value or a mixture of both.

In the first case, each type is represented by QueryTypeID. This object is constructed by pointing to a specific GUID representing the type of an element, the so called Type_UUID.

Example

In the following example, elements valid for selection are only columns and beams, located on active documents and layers:

type_uuids = [AllplanIFW.QueryTypeID(AllplanElementAdapter.Beam_TypeUUID),  #(1)!
              AllplanIFW.QueryTypeID(AllplanElementAdapter.Column_TypeUUID)]

selection_query = AllplanIFW.SelectionQuery(type_uuids)

ele_select_filter = AllplanIFW.ElementSelectFilterSetting(selection_query,
                                                          bSnoopAllElements = False)
  1. The GUIDs of all possible element types are defined as constants inside the NemAll_Python_IFW_ElementAdapter module. For better readability, point to the constant in your code, rather than to UUID explicitly.

Tip

The element types have nothing to do with Python classes. It may happen, that two different python objects of the same class represent two elements with different Type_UUIDs in the DF. An example of that is ModelElement3D:

  • when the geometry object used to construct it was a polyhedron the resulting element will be of type Volume3D_TypeUUID
  • when the geometry object used to construct it was a brep the resulting element will be of type BRep3D_Volume_TypeUUID

You can check the type of the element by calling GetElementAdapterType.GetTypeName on the element adapter

In the second case, you construct a filter by defining a callable class (class with a __call__ member). We called it CustomFilter on the graph above, but name it as you want.

The __call__ member must have one argument of type BaseElementAdapter and must return a bool value.

Example

In the following example, elements valid for selection are the one represented with a polyhedron with exactly 6 faces. The elements must be located on active documents and layers:

class CustomFilter():
    def __call__(self, element: AllplanElementAdapter.BaseElementAdapter) -> bool:
        if isinstance(geo := element.GetModelGeometry(), AllplanGeometry.Polyhedron3D):
            return geo.GetFacesCount() == 6
        return False

selection_query = AllplanIFW.SelectionQuery(CustomFilter())

ele_select_filter = AllplanIFW.ElementSelectFilterSetting(selection_query,
                                                          bSnoopAllElements = False)

You can also mix the list of element types and your custom filter. The list with element types and your filter will be added together with AND operand. This means, that an element to be valid for selection, must be of type in the list AND satisfy the custom filter

Example

In the following example, elements valid for selection are only columns and beams, represented by a polyhedron with exactly 6 faces. The elements must be located on active documents and layers:

type_uuids = [AllplanIFW.QueryTypeID(AllplanElementAdapter.Beam_TypeUUID),
              AllplanIFW.QueryTypeID(AllplanElementAdapter.Column_TypeUUID)]

class CustomFilter():
    def __call__(self, element: AllplanElementAdapter.BaseElementAdapter) -> bool:
        if isinstance(geo := element.GetModelGeometry(), AllplanGeometry.Polyhedron3D):
            return geo.GetFacesCount() == 6
        return False

selection_query = AllplanIFW.SelectionQuery([CustomFilter()] + type_uuids)

ele_select_filter = AllplanIFW.ElementSelectFilterSetting(selection_query,
                                                          bSnoopAllElements = False)