icon

Forum de la communauté Allplan

[Question] Useability of PythonParts for automatisation tasks


Hello,

I have some general questions/observations regarding the useability of PythonParts. In my experience developing with PythonParts, I’ve noticed that they are often far less dynamic than expected. In most of my applications, I modify their representation at the placement stage, but once the PythonPart is finalised, no further modifications are possible. I’ve identified two main issues behind this limitation:

1. ElementResult as a PythonPart vs ArchitecturalElement: If I define the ElementResult as a PythonPart (rather than a subclass of ArchitecturalElement), the resulting object remains parametrised and editable later. However, it loses many key functionalities that normal architectural elements have, such as the addition of recesses, openings, and reporting. Thus, generating a PythonPart object rather than actual architectural elements remain impractical in real workflows, as re-implementing all these functionalities (openings, joints, niches, etc.) feels redundant and it is misaligned with the practices of engineers in our office.

2. Automation limitations: When a PythonPart defines a unique and complex object in the model, manual editing works fine (utilising the parametric nature of the PythonPart). However, in my applications that aim to automate tasks requiring hundreds of actions, it’s unfortunate that PythonParts can only be updated manually, and they cannot automatically respond to changes in associated ArchitecturalElements. For example, if I create a PythonPart that annotates a wall, I would expect it to update automatically when the wall changes. As far as I understand, this is currently unachievable, which significantly limits the useability of PythonParts. Additionally, it seems that only one PythonPart can be defined per ScriptObject, so generating multiple PythonParts is unfeasible.
Because of these two main issues, I find that the practical utility of PythonParts in my applications is much more limited than it ideally should be.

My question is: have others encountered these limitations, and if so, how have you worked around them?

Thanks in advance for your help!

Show most helpful answer Hide most helpful answer

Hi,

Maybe I can also put some light into the room

1. Native elements vs. PythonParts
If I create a slab by passing an AllplanArchElements.SlabElement directly into the ModelEleList of CreateElementResult, the result is a native Allplan element. In this case, I can add recesses to it just as I would with a manually created slab.
However, if I wrap the same slab inside a PythonPart as:

model_ele_list = []
model_ele_list.append(slab_element)
pyp_util = PythonPartUtil()
pyp_util.add_pythonpart_view_2d3d(model_ele_list)
pythonpart = pyp_util.create_pythonpart(self.build_ele)
return CreateElementResult(
elements=pythonpart,
placement_point=AllplanGeometry.Point2D()
)

Please don't wrap an architectural element this way. PythonParts are just Smart Symbols with the additional possibility of reactivating them with double-click. Smart Symbols in ALLPLAN are not meant to wrap architectural elements. When you do this, you remove all their "architectural" behaviours: interactions with other elements (like openings) or binding to the level model doesn't work anymore.

If you want to preserve this architectural behaviour, you need to link the elements to your PythonPart as child elements. Keep in mind, that you PythonPart cannot consist solely of child elements. It need some kind of a graphical representation. It might be just a dummy, unprintable box. And also keep in mind, that any subsequent modification of the PythonPart recreates all its child elements - you lose any changes you might have made to them.

This is a compromise - a PythonPart as well as architectural elements have some special behaviour. In a PythonPart - you define this behaviour. There is no way to simply combine them both.

I am trying to establish an association between architectural elements and custom annotations created via Python. The approach you suggested seems very promising, but I am still unclear on how the connection is actually implemented.

Christophe already linked this article, which is exactly a step-by-step implementation guide. Is there a specific part that is not clear or some topic is missing? Alternatively, have a look at this course - I showcase, how a PythonPart can be linked to an existing beam, so that reinforcement react and adapt to the changes on that beam. This is exactly your use-case.

The API reference mentions the ConnectToElements class, while the example script uses ElementGeometryConnection; however, it is not entirely clear how either of these establishes a link between the architectural element and the PythonPart.

A directional relationships between these two is established. It happens via the association framework of ALLPLAN. The result of this association is: the PythonPart is updated whenever the associated element has changed. Updated means (from the docs):

Under the hood, during the update the framework simulates reactivation of the PythonPart and terminating it directly after, just like the user would double-click on PythonPart and then immediately hit Esc. The palette and handles are suppressed during this process.

And this should answer your next question:

Additionally, I would like to understand what part of the PythonPart is re-executed when an associated architectural element is modified. Is it only the execute() method, or are other parts of the script re-run as well?

Executed is exactly the same part of the code, that would be executed as if you would double-click on the PythonPart and terminated it with ESC. The same script flow applies.
The only thing, that is different, is the value of the execution_event of your ScriptObject instance. You may (but don't have to) route the logic of your script differently, depending on whether your PythonPart is updated or actually modified by the user.

Finally, is there a way to convert an existing PythonPart into standard architectural or geometry elements?

You mean a native function to do this? Yes, with Unlink Smart Symbols. Try to experiment with a different modes (fully, structured) and see the effects. They might differ, depending on whether you wrapped the elements within PythonPart or you created them as child elements of the PythonPart.

Cheers,
Bart

Hi,

1) Native elements vs PythonParts
Native Allplan elements (walls, slabs, etc.) can be created by script, and the result behaves like any standard architectural element, with support for openings, joints, and so on.
These native elements can then be modified with the usual Allplan tools, and also by script again if needed.
source

The PythonPart, as a single parametric object with a palette and stored parameters, is a different approach.

2) Linking native elements to a PythonPart
A PythonPart can be linked to one or several native elements.
When creating this connection, you can define which kinds of changes in the native elements will or will not trigger an update of the PythonPart.
source

Finally, you mention the notion of a transaction.
With an Interactor PythonPart, it is possible to create several transactions, and therefore several PythonParts.
There is also a concept of hierarchy, which may be useful for your use case.

In summary, PythonParts can be used in many different ways (for creating and modifying objects), with several levels of complexity, ranging from no‑code (Visual Scripting) up to Interactor‑based solutions.

Best
Christophe

Thank you for the very informative response.
I've experimented with the functionalities you suggested and would appreciate some further clarification on a few points:

1. Native elements vs. PythonParts
If I create a slab by passing an AllplanArchElements.SlabElement directly into the ModelEleList of CreateElementResult, the result is a native Allplan element. In this case, I can add recesses to it just as I would with a manually created slab.
However, if I wrap the same slab inside a PythonPart as:

model_ele_list = []
model_ele_list.append(slab_element)
pyp_util = PythonPartUtil()
pyp_util.add_pythonpart_view_2d3d(model_ele_list)
pythonpart = pyp_util.create_pythonpart(self.build_ele)
return CreateElementResult(
    elements=pythonpart,
    placement_point=AllplanGeometry.Point2D()
)

Then I can no longer insert a recess into the slab via the Allplan GUI. Could you clarify what I do incorrectly in this case?

A related question: Suppose I have two scripts: one that generates architectural elements, and another that performs additional modifications on those elements. In the second script, previously, I filtered selections using MultiElementSelectInteractor with something like WallTier_TypeUUID. If the architectural elements are instead wrapped inside PythonParts, what would be the correct filtering approach? Is the intended workflow to select PythonParts first and then inspect their child elements to check whether they include a WallTier_TypeUUID element?

2. Linking native elements to a PythonPart
I am trying to establish an association between architectural elements and custom annotations created via Python. The approach you suggested seems very promissing, but I am still unclear on how the connection is actually implemented.
The API reference mentions the ConnectToElements class, while the example script uses ElementGeometryConnection; however, it is not entirely clear how either of these establishes a link between the architectural element and the PythonPart.
Additionally, I would like to understand what part of the PythonPart is re-executed when an associated architectural element is modified. Is it only the execute() method, or are other parts of the script re-run as well?

Finally, is there a way to convert an existing PythonPart into standard architectural or geometry elements?
For example, if I have a PythonPart composed of a slab and two lines, is it possible to “explode” it to expose and access those three underlying elements?

Thanks again for your kind help!

Hi,

Maybe I can also put some light into the room

1. Native elements vs. PythonParts
If I create a slab by passing an AllplanArchElements.SlabElement directly into the ModelEleList of CreateElementResult, the result is a native Allplan element. In this case, I can add recesses to it just as I would with a manually created slab.
However, if I wrap the same slab inside a PythonPart as:

model_ele_list = []
model_ele_list.append(slab_element)
pyp_util = PythonPartUtil()
pyp_util.add_pythonpart_view_2d3d(model_ele_list)
pythonpart = pyp_util.create_pythonpart(self.build_ele)
return CreateElementResult(
elements=pythonpart,
placement_point=AllplanGeometry.Point2D()
)

Please don't wrap an architectural element this way. PythonParts are just Smart Symbols with the additional possibility of reactivating them with double-click. Smart Symbols in ALLPLAN are not meant to wrap architectural elements. When you do this, you remove all their "architectural" behaviours: interactions with other elements (like openings) or binding to the level model doesn't work anymore.

If you want to preserve this architectural behaviour, you need to link the elements to your PythonPart as child elements. Keep in mind, that you PythonPart cannot consist solely of child elements. It need some kind of a graphical representation. It might be just a dummy, unprintable box. And also keep in mind, that any subsequent modification of the PythonPart recreates all its child elements - you lose any changes you might have made to them.

This is a compromise - a PythonPart as well as architectural elements have some special behaviour. In a PythonPart - you define this behaviour. There is no way to simply combine them both.

I am trying to establish an association between architectural elements and custom annotations created via Python. The approach you suggested seems very promising, but I am still unclear on how the connection is actually implemented.

Christophe already linked this article, which is exactly a step-by-step implementation guide. Is there a specific part that is not clear or some topic is missing? Alternatively, have a look at this course - I showcase, how a PythonPart can be linked to an existing beam, so that reinforcement react and adapt to the changes on that beam. This is exactly your use-case.

The API reference mentions the ConnectToElements class, while the example script uses ElementGeometryConnection; however, it is not entirely clear how either of these establishes a link between the architectural element and the PythonPart.

A directional relationships between these two is established. It happens via the association framework of ALLPLAN. The result of this association is: the PythonPart is updated whenever the associated element has changed. Updated means (from the docs):

Under the hood, during the update the framework simulates reactivation of the PythonPart and terminating it directly after, just like the user would double-click on PythonPart and then immediately hit Esc. The palette and handles are suppressed during this process.

And this should answer your next question:

Additionally, I would like to understand what part of the PythonPart is re-executed when an associated architectural element is modified. Is it only the execute() method, or are other parts of the script re-run as well?

Executed is exactly the same part of the code, that would be executed as if you would double-click on the PythonPart and terminated it with ESC. The same script flow applies.
The only thing, that is different, is the value of the execution_event of your ScriptObject instance. You may (but don't have to) route the logic of your script differently, depending on whether your PythonPart is updated or actually modified by the user.

Finally, is there a way to convert an existing PythonPart into standard architectural or geometry elements?

You mean a native function to do this? Yes, with Unlink Smart Symbols. Try to experiment with a different modes (fully, structured) and see the effects. They might differ, depending on whether you wrapped the elements within PythonPart or you created them as child elements of the PythonPart.

Cheers,
Bart