Skip to content

Property palette

A PythonPart (in the meaning of an Allplan extension) is characterized by its parameters. A parameter itself (type, name, etc.) is defined by the developer, but its value is input by the user in the property palette: part of the Allplan UI.

Info

Property palette is the key feature of the PythonParts framework, because it removes the burden of building the UI1 from the developer. It's enough to define the parameters in the PYP file - the framework will take care of showing the right control in the property palette.

Page node

The parameters are defined and grouped in <Page> nodes. This node represents the tabs shown on the right side of the property palette. This helps to group the parameters into meaningful sections. The node is defined as follows:

<?xml version =”1.0” encoding =”utf-8”?>
<Element>
    <Script>
    ... <!--(1)! -->
    </Script>

    <Page>
        <Name>FirstPage</Name>
        <Text>First page</Text>
        <TextId>1001</TextId>
        <Visible>True</Visible>
        <Enable>True</Enable>

        <Parameter>
        ... <!--(2)! -->
        </Parameter>
    </Page>

    <Page>
    ... <!--(3)! -->
    </Page>
    ...
</Element>
  1. The tag for basic meta-data of a PythonPart. Refer to the previous article about PYP file to learn, what must be put in here.

  2. The definition of the parameter.

  3. Tab control

    Defining more than one <Page> will result in a tab control like this to appear.

    If there is only one page defined, but without any <Parameter>, an empty palette is shown.

    If there are more than one <Page> nodes, but some of them have no <Parameter>, the tabs of the empty ones are not shown at all.

Tag Required Default Description
<Name> required None Name of the page. You can refer to it in the script e.g., to handle an event of switching a page
<Text> required None Text displayed on the tab.
<TextId> optional None ID of the translated string resource to localize the <Text>
<Visible> optional True Condition for showing or hiding the entire page. Refer to this article to learn, how a condition can be defined.
<Enable> optional True Condition for disabling/enabling all the controls on the page.

Tip

The event of user switching the page can be handled using set_active_palette_page function. Learn more here.

Hidden page

A special page, univisible to the user, can be created with the name __HiddenPage__.

<Page>
    <Name>__HiddenPage__</Name>
    <Text></Text>
    <Parameter>
    ...
    </Parameter>

</Page>

All parameters added to this page are hidden and can be used to add extra parameters unvisible for the user, but accessible in the script. Use cases for such parameter are e.g.:

  • object UUIDs for selected elements, which are used to retrieve the new element data in the PythonPart modification
  • geometry values, needed for the PythonPart modification
  • Attributes, which are set in the PY file

Parameters

The node <Parameter> is used to define the parameter of the PythonPart,as well as the layout and controls of the property palette. The order of the <Parameter> nodes within the <Page> determines the order of the controls on the property palette. Here is a basic example of a parameter:

PropertyPalette

<Parameter>
    <Name>MyParameter</Name>
    <Text>My parameter</Text>
    <Value>123</Value>
    <ValueType>Integer</ValueType><!--(1)!-->
</Parameter>
  1. The <ValueType> determines, what type of control will be shown in the palette. Refer to the subchapters to learn, what value types are possible.

For the full list of tags, which can be included under <Parameter> with their explanation please refer to the chapter all tags.

Refer to a parameter

For each parameter, an object of type ParameterProperty is being created and added to the BuildingElement object as an instance attribute. The name of the attribute equals the name of the parameter defined in the <Name> tag.

BuildingElement is passed as an argument to various functions of the script (learn more about them here or here).

Example

Let's assume we defined a parameter MyParameter in the PYP file, as shown above. When a PythonPart is started, the BuildingElement is constructed by the framework and passed, e.g. to create_element function. You can then access the value of this parameter inside this function like:

def create_element(build_ele: BuildingElement, #(1)!
                   doc: AllplanElementAdapter.DocumentAdapter):

    my_parameter = build_ele.MyParameter.value #(2)!
    print("The value of MyParameter is: " + str(my_parameter))
  1. The BuildingElement object has the MyParameter in an instance attribute named the same as the parameter, so calling build_ele.MyParameter will get you ParameterProperty object.
  2. To get the value, refer to the value attribute of the ParameterProperty object
Result in Allplan trace window
The value of MyParameter is: 123

Localize parameter

The texts in the property palette can be localized by providing string resource IDs in an appropriate tag. The table below shows, which tag is to be used for this purpose. To learn more about localizing your PythonPart, refer to this article

Tag to localize Tag to use for localization IDs
<Text> <TextId>
<Value> <ValueTextId>
<ValueTextList> <ValueTextIdList>

Example

<Parameter>
    <Name>String</Name>
    <Text>String parameter</Text>
    <TextId>1001</TextId>
    <Value>This is a string</Value>
    <ValueTextId>1002</ValueTextId>
    <ValueType>String</ValueType>
</Parameter>

Tip

In addition to your own predefined IDs, you can also use standard IDs defined internally in Allplan. To do this, instead of an integer, provide an option from a dedicated enumeration class. They are available in the AllplanSettings module. Their names begin with TextRes. E.g. TextResSillType contains IDs pointing to texts about window sill types available in Allplan.

<ValueTextIdList>AllplanSettings.TextResShapeType.eInner|
                 AllplanSettings.TextResShapeType.eOuter<ValueTextIdList>

The same parameter on multiple pages

You can define multiple <Parameter> with the same <Name> in the PYP file, and they will be considered as the same parameter by the PythonParts framework. Meaning, that changing the value of one of them, will change the value of the other one. Both can appear on multiple pages of the property palette.

Example

An example of using a parameter on multiple pages is shown in AllControls

  • …\etc\Examples\PythonParts\PaletteExamples\AllControls.pyp
  • …\etc\PythonPartsExampleScripts\PaletteExamples\AllControls.py

Styling

Set up the size

If a control is in a row, the height and width can be defined by the optional tags <HeightInRow> and <WidthInRow>. The values must be provided in pixels. The size values can be used, for example, to make the image in a button control more visible.

Control size

Set up the text style

The style of the text displayed on the left side can be controlled with the tag <FontFaceCode>

Text style

<Parameter>
    <Name>Integer</Name>
    <Text>Integer parameter</Text>
    <Value>123</Value>
    <ValueType>Integer</ValueType>
    <FontFaceCode>2</FontFaceCode> <!--(1)!-->
    <FontStyle>1</FontStyle>       <!--(2)!-->
</Parameter>
  1. This controls the font style (italic, weight) according to the table below.

    <FontStyle> Description
    0 normal
    1 bold
    2 italic
    4 underline
  2. This tag controls the size The default value is 1. The higher the number, the larger the text.

Parameter persistency

Using the optional tag <Persistent> it is possible to control the persistency of the parameter. This means, whether the parameter should be stored in the favorite file (.pyv file) and/or in the model in the PythonPart itself.

<Parameter>
    <Name>Integer</Name>
    <Text>Integer parameter</Text>
    <Value>123</Value>
    <ValueType>Integer</ValueType>
    <Persistent>Model</Persistent>
</Parameter>
Setting Parameter value is...
Model saved in PythonPart only
Favorite saved in favorite file only
ModelAndFavorite saved in both PythonPart and favorite file
No not saved at all

Include parameter

Within a .pyp file it is possible to include the parameters of another .pyp file by using a special parameter with the <ValueType> set to Include. A typical use case for that can be e.g., to include:

  • files with general PythonPart parameter
  • files with parameter blocks for indexing

into the current .pyp file. Using Include parameter allows to define these parameters only once. This avoids a lot of typing and simplifies the maintenance of the .pyp file.

Here is a simple example of including parameters from another .pyp file:

<Parameter>
    <Name></Name>
    <Text></Text>
    <Value>std\ParameterIncludes\TextProperties.incpyp</Value><!--(1)!-->
    <ValueType>Include</ValueType>
    <Visible>Alignment:False</Visible><!--(2)!-->
</Parameter>
  1. Path to the included file can be define relatively to the current .pyp file or, as here demonstrated, with a leading std\, usr\ or prj\ to make it absolute.

    Note, that the included file can have any extension. A good practice is to use the .incpyp

  2. You can prevent individual parameters from being included by adding the <Visible> tag like this. separate multiple parameters with |, like this:

    <Visible>Alignment:False|BackgroundFillOfViewport:False</Visible>
    

Warning

When the PythonPart is moved/copied by the user within the Allplan library the main .pyp file is moved/copied, but the included files are not. At the same time, the path defined in <Value> tag remains unchanged. If it is a path relative to .pyp file, it will point to a nonexistent file after moving the main file. For that case, we recommend providing an absolute path e.g., with leading std\, usr\ or prj\.

Tip

In the following folder:

…\Etc\PythonPartsFramework\ParameterIncludes\

you can find .incl files, which contain a set of parameters for specific types of objects. You can use them, to save work needed for defining properties parameters for standard objects, such as Text.

Info

The old #include directive is soft-deprecated.

Multiple include

It's possible to include a single .incpyp file multiple times at once, like:

<Parameter>
    <Name>Left;2-4;Right</Name><!--(1)!-->
    <Text></Text>
    <TextId>1002;;1003</TextId><!--(2)!-->
    <Value>IncludeParameter.incpyp</Value><!--(3)!-->
    <LanguageFile>IncludeParameter_incpyp</LanguageFile><!--(4)!-->
    <ValueType>Include</ValueType>
</Parameter>
  1. Indices defined here will be added as suffix to the names of parameters in the included file. They will also replace the # placeholder in all existing <Text> tags from the included file. The value can be a string, a single number or an index range like 2-4.
  2. If the indices in the <Name> tag are texts, here you can provide their ID in the string resource file to localize them. For the range 2-4 we must provide an empty entry.
  3. Path to the included file to include. The path can be relative to the current .pyp file or with a leading std\, usr\ or prj\ to make it globally a standard Allplan path.
  4. Path to the string resource file of the included file. It can be relative to the current pyp file or with a leading std\, usr\ or prj\ to make it globally a standard Allplan path. It's optional. If not defined, the text for the IDs is taken from the resource file of the pyp file.

Note

The # character will be replaced with an index also inside the <Visible> or <Enable> tags. If the index is defined as a range, it is possible to define the formula like this:

<Visible>CoordCount .gt. #</Visible>

Example

In this example, the included file contains one parameter Length. It is being included using the #include statement into the target file.

<Page>
    <Name>FirstPage</Name>
    <Text>Page text 1</Text>
    <TextId>1001</TextId>

    <Parameter>
        <Name>Left;2-4;Right</Name><!--(3)!-->
        <Text></Text>
        <TextId>1002;<!--(2)!-->;1003</TextId>
        <Value>IncludeParameter.incpyp<!--(1)!--></Value>
        <LanguageFile>IncludeParameter_incpyp</LanguageFile>
        <ValueType>Include</ValueType>
    </Parameter>
</Page>
  1. The file suffix does not have to be .incpyp. Parameter from regular .pyp files can also be included.
  2. Note, that there is no TextId specified here, as the index is defined as a range!
  3. With indices defined like this, we will be including the parameter 5 times in total, as: Left, 2, 3, 4, and Right.
<Include>
    <Parameter>
        <Name>Length</Name>
        <Text>Length #<!--(1)!--></Text>
        <TextId>2000</TextId>
        <Value>1000</Value>
        <ValueType>Length</ValueType>
    </Parameter>
</Include>
  1. The # placeholder will be replaced with the indices defined in the <Name> tag of the Include parameter in the target file.
<Item>
    <TextId>1002</TextId>
    <Text>links</Text>
</Item>
<Item>
    <TextId>1003</TextId>
    <Text>rechts</Text>
</Item>
<Item>
    <TextId>2000</TextId>
    <Text>Länge #<!--(1)!--></Text>
</Item>
  1. As the string in the <Text> tag in the source .pyp file contains the # placeholder, it must also be specified in here.
<Page>
    <Name>FirstPage</Name>
    <Text>Page text 1</Text>
    <TextId>1001</TextId>

    <Parameter>
      <Name>LengthLeft</Name>
      <Text>Länge links</Text>
      <Value>1000</Value>
      <ValueType>Length</ValueType>
    </Parameter>
    <Parameter>
      <Name>Length2</Name>
      <Text>Länge 2</Text>
      <Value>1000</Value>
      <ValueType>Length</ValueType>
    </Parameter>
    <Parameter>
      <Name>Length3</Name>
      <Text>Länge 3</Text>
      <Value>1000</Value>
      <ValueType>Length</ValueType>
    </Parameter>
    <Parameter>
      <Name>Length4</Name>
      <Text>Länge 4</Text>
      <Value>1000</Value>
      <ValueType>Length</ValueType>
    </Parameter>
    <Parameter>
      <Name>LengthRight</Name>
      <Text>Länge rechts</Text>
      <Value>1000</Value>
      <ValueType>Length</ValueType>
    </Parameter>
</Page>

The use of the include statement is also shown in the example IncludeParameter, located in:

  • …\etc\Examples\PythonParts\PaletteExamples\OptionalTags\IncludeParameter.pyp
  • …\etc\PythonPartsExampleScripts\PaletteExamples\OptionalTags\IncludeParameter.py

References to included parameters

If the included file contains parameters, that refer to another parameter in this file e.g., to control the visibility, we must use the $ placeholder to consider the indices defined in the <Name> tag of the referred parameter name.

Example

In this example, the included file contains two parameters:

  • Height - a length parameter
  • ShowHeight - a check box, that should control the visibility of the Height parameter

The target file includes the parameter from the included file two times, with different indices: Wall and Beam

<Parameter>
    <Name>Wall;Beam</Name>
    <Text></Text>
    <Value>IncludeParameter.incpyp</Value>
    <ValueType>Include</ValueType>
</Parameter>
  1. With this #include statement, the parameters from the included file will be included two times in total, each time with a different index.
<Parameter>
    <Name>Height</Name>
    <Text># height<!--(2)!--></Text>
    <Value>3000</Value>
    <ValueType>Length</ValueType>
    <Visible>ShowHeight$<!--(1)!--></Visible>
</Parameter>
<Parameter>
    <Name>ShowHeight</Name>
    <Text>Show # height</Text>
    <Value>True</Value>
    <ValueType>Checkbox</ValueType>
</Parameter>
  1. The $ placeholder will be replaced with the string Wall or Beam from the <Name> tag of the include parameter, creating a reference to a specific parameter as a result
  2. The # placeholder will be replaced with the string Wall or Beam, creating a specific text in the palette.
<Page>
    <Name>FirstPage</Name>
    <Text>First page</Text>

    <Parameter>
        <Name>HeightWall</Name>
        <Text>Wall height</Text>
        <Value>3000</Value>
        <ValueType>Length</ValueType>
        <Visible>ShowHeightWall<!--(1)!--></Visible>
    </Parameter>

    <Parameter>
        <Name>ShowHeightWall</Name>
        <Text>Show Wall height</Text>
        <Value>True</Value>
        <ValueType>Checkbox</ValueType>
    </Parameter>

    <Parameter>
        <Name>HeightBeam</Name>
        <Text>Beam height</Text>
        <Value>3000</Value>
        <ValueType>Length</ValueType>
        <Visible>ShowHeightBeam<!--(2)!--></Visible>
    </Parameter>

    <Parameter>
        <Name>ShowHeightBeam</Name>
        <Text>Show Beam height</Text>
        <Value>True</Value>
        <ValueType>Checkbox</ValueType>
    </Parameter>

</Page>
  1. The $ was replaced with Wall, creating the correct reference to the ShowHeightWall parameter, ensuring the correct visibility behavior of the control.
  2. The $ was replaced with Beam, creating the correct reference to the ShowHeightBeam parameter, ensuring the correct visibility behavior of the control.

Migrate parameter

When you release a new version of your PythonPart and you renamed, added, or deleted some parameters, the PythonPart elements created with the older version will still contain the old parameters. When a user then reactivates it with double-click, the framework will construct the BuildingElement with parameter names from new PYP file and will try to assign them values from the modified PythonPart element. However, it won't be able to assign values to parameters with changed names, so it will assign the default values from the new PYP file. This may lead to unwanted changes of the PythonPart element and the user will not be aware of them!

To prevent this, the parameter must be migrated by implementing the migrate_parameter() function. It will be called every time a PythonPart is run in modification mode (e.g., by a double click). The implementation must be done as follows:

def migrate_parameter(parameter_list: List[str],
                      version       : float):
    """ migrate the parameter

    Args:
        parameter_list: parameter list
        version:        PythonPart version
    """

Version check

The version of the current PythonPart is passed as a float in the argument version. The check, if the migration is necessary, can be done in the script like this:

if version < 1.9:
    return

Parameter conversion

With the argument parameter_list all the arguments are passed to the function as a list, where in each entry a parameter is represented with a string (regardless its value type e.g., Radius=500.0\n). The conversion of the parameters can be done using the functions provided in the BuildingElementMigrationUtil or by a custom migration implementation.

Example

Let's say with the version 2.0 we did following changes in our PythonPart:

  • the parameter Minor was renamed to MinorRadius
  • the parameter Major was renamed to MajorDiameter
  • the value of the new parameter MajorDiameter must be doubled, as it now represents a diameter instead of radius

Here is how the migration can be implemented:

def migrate_parameter(parameter_list: List[str],
                      version       : float):
    if version < 1.9:
        return

    BuildingElementMigrationUtil.\
        transfer_parameter_value(parameter_list = parameter_list,
                                 old_element_id = "",#(1)!
                                 old_value_name = "Minor",
                                 new_element_id = "",
                                 new_value_name = "MinorRadius")

    BuildingElementMigrationUtil.\
        transfer_parameter_value(parameter_list     = parameter_list,
                                 old_element_id     = "",
                                 old_value_name     = "Major",
                                 new_element_id     = "",
                                 new_value_name     = "MajorDiameter",
                                 converter_function = lambda value: str(float(value) * 2)) #(2)!
  1. In case of operating with more than one BuildingElement objects (more than one .pyp file for palette and PythonPart creation), a parameter can be migrated from one BuildingElement object to another. In this case (and in 99% of other) there is only one .pyp file, so we provide an empty string here.
  2. As a conversion of a parameter value was necessary, conversion function must be provided as an argument. This conversion function must be defined as conversion_function(value: str) -> str, meaning the input as well as output value must always be a string, regardless of what type of parameter value is converted.

Constants

In some cases, it's better to use named constants instead of magic numbers inside the PYP and PY files. Syntax like RED or SET_LENGTH is much more readable than 6 or 1001.

You can define these kind of constants in the PYP file in the <Constants> section. Define this section between <Script> and <Page> sections.

</Script>

<Constants>
    <Constant>
        <Name>RED</Name>
        <Value>6</Value>
        <ValueType>Integer</ValueType>
    </Constant>
</Constants>

<Page>

You can then access the constants in both PYP and PY files

Refer to the constants in the tags <Value>, <ValueList> and <EventId> by their name:

Tag Example
<Value> <Value>GREEN</Value>
<ValueList> <ValueList>LENGTH_1|LENGTH_2|LENGTH_3</ValueList>
<EventId> <EventId>SET_LENGTH</EventId>

The constants are part of the BuildingElement object and can be accessed with their names like:

if value == build_ele.LENGTH_1:
    build_ele.Width.value = 1000.0

...

if event_id == build_ele.SET_LENGTH:
    build_ele.Length.value = 1000.0

Example

For a complete usage of the constants see the examples:

  • ...\etc\Examples\PythonParts\PaletteExamples\ ButtonList.pyp
  • ...\etc\PythonPartsExampleScripts\PaletteExamples\ ButtonList.py
  • ...\etc\Examples\PythonParts\PaletteExamples\ Buttons.pyp
  • ...\etc\PythonPartsExampleScripts\PaletteExamples\ Buttons.py
  • ...\etc\Examples\PythonParts\PaletteExamples\ ComboBox.pyp
  • ...\etc\PythonPartsExampleScripts\PaletteExamples\ ComboBox.py
  • ...\etc\Examples\PythonParts\PaletteExamples\ RadioButtons.pyp
  • ...\etc\PythonPartsExampleScripts\PaletteExamples\ RadioButtons.py

  1. When your UI requirements exceeds the capabilities of PythonParts framework, you can build your own user interface. We recommend achieving it by creating a separate .NET application and loading its assemblies using pythonnet (the package is already delivered with Allplan installation). Have a look on the example UsingClrInteractor