Next step for the modeling of our PythonPart “Reinforced Concrete Column”, today let’s see how to configure the anchors of our 3D object.

By anchoring I am particularly thinking of 2 types :

  1. altimetry management ;
  2. the insertion point in plan view.

1) GUI Script

  • Altimetry

Much more than a simple 3D object positioned without any intelligence, the Allplan API allows us to automatically hook our PythonPart to the levels present in the building structure.

To do this, we will complete our previously used “Height” variable ; for the record, we had set up a “Length” field, a generic type for entering a length.

Indeed, to allow us to define a reference at a level, we will first set up the dialog box used by the standard architectural elements :

Please note :

There are 3 ways to define a reference to a plane :

  • top edge entry ;
  • bottom edge entry ;
  • both.

We will choose this last hypothesis.

 <Parameter>
<Name>PlaneReferences</Name>
<Text> </Text>
<Value></Value>
<ValueType>PlaneReferences</ValueType>
<ValueDialog>PlaneReferences</ValueDialog>
</Parameter>

In order to obtain a better user experience, we will link our Height field to this PlaneReferences by adding a new tag named Constraint.

In this way Allplan will automatically calculate and display the absolute height of our object.

Here is the syntax :

 <Parameter>
<Name>ColumnHeight</Name>
<Text>Hauteur</Text>
<FontFaceCode>4</FontFaceCode>
<Value>-1</Value>
<ValueType>Length</ValueType>
<Constraint>PlaneReferences</Constraint>
</Parameter>

 <Parameter>
<Name>PlaneReferences</Name>
<Text> </Text>
<Value></Value>
<ValueType>PlaneReferences</ValueType>
<ValueDialog>PlaneReferences</ValueDialog>
<Constraint>ColumnHeight</Constraint>
</Parameter>

Please note : this tag “Constraint” was added in the 2 fields in order to create a bidirectional connection

Back in Allplan, I obtain :

If I modify the height as follows :

the value updates :

Inversely, when I change my value in the palette :

the fields in the dialog box are correctly updated :

  • Insertion Point

In the first chapter of this example, we set up a column that could be rectangular or circular.

With the parameters entered we obtain :

Following the same logic as for elevation, we will replicate the native behavior of Allplan with architectural elements, specifically the columns.

I’ll create a new Expander dedicated to this function :

<Parameter>

<Name>AttachmentExpander</Name>

<Text>Accrochage</Text>

<ValueType>Expander</ValueType>

</Parameter>

then I add the following fields :

<Parameter>
<Name>AttachmentPointButtonRow</Name>
<Text>Point d’accrochage</Text>
<ValueType>Row</ValueType>

 

<Parameter>
<Name>AttachmentPoint</Name>
<Text>Attachment point index</Text>
<Value>5</Value>
<ValueType>RefPointButton</ValueType>
</Parameter>

 

</Parameter>

Please note :

  • I place my button in a Row and I complete the text that will be displayed in Allplan ;
  • with 5 as the default Value, we propose a center anchor.

Back in Allplan, I obtain :

Here is the complete file :

2) Main Script

I start by completing my list of imports :

import NemAll_Python_ArchElements as ArchElements
  • Fonction create_element

Then I go to the create_element function to complete the list of retrieved information :

attach_point = build_ele.AttachmentPoint.value

 

plane_ref: ArchElements.PlaneReferences = build_ele.PlaneReferences.value

Next I create a new variable to get the bottom level of my object :

column_bottom = plane_ref.GetAbsBottomElevation()

This level, along with the height (column_height), allows us to model our object.

  • Class Objects3D

I will complete my parent class Objects3D to integrate the 3 anchor values (X, Y, and Z):

class Objects3D:

def __init__(self, object_prop, attach_point, column_bottom):
        self.object_prop = object_prop
        self.geo = None
        self.attach_point = attach_point
        self.x_offset = 0
        self.y_offset = 0
        self.z_offset = column_bottom

Please note : I store the reference point separately and leave the X and Y offsets at 0 by default because the calculation will depend on the shape of the column.

I’m preparing my future attachment_point function to calculate this offset :

def attachment_point(self):
        pass
  • Class Cuboid

I move to my child class Cuboid and complete it :

class Cuboid(Objects3D):

 

def __init__(self, object_prop, attach_point, column_bottom, column_length, column_thick, column_height):
        Objects3D.__init__(self, object_prop, attach_point, column_bottom)
        self.column_length = column_length
        self.column_thick = column_thick
        self.column_height = column_height
        self.name_dim = f”{round(column_length / 10)}x{round(column_thick / 10)}”
        self.placement_pt = self.attachment_point()

To fully understand the placement in plan view, it’s necessary to keep in mind the values returned by our AttachmentPoint, which I remind here :

By default, our rectangular column is placed from the lower-left corner, so :

  • in X
    1. values on the left (1, 4, 7) are ignored ;
    2. values in the middle (2, 5, 8) will result in an offset equal to half of the length ;
    3. values on the right (3, 6, 9) induce an offset equal to the length.

 

  • in Y
    1. values at the top (1, 2, 3) impose an offset equal to the thickness ;
    2. values in the middle (4, 5, 6) will result in an offset equal to half of the thickness ;
    3. values at the bottom (7, 8, 9) are ignored.

In Python, I translate this informations with the following two conditions :

def attachment_point(self):

 

if self.attach_point in {2, 5, 8}:
        self.x_offset = -self.column_length / 2
elif self.attach_point in {3, 6, 9}:
        self.x_offset = -self.column_length

 

if self.attach_point in {1, 2, 3}:
        self.y_offset = -self.column_thick
elif self.attach_point in {4, 5, 6}:
        self.y_offset = -self.column_thick / 2

 

placement_pt = Geometry.Point3D(self.x_offset, self.y_offset, self.z_offset)

 

return placement_pt

Please note : I concatenate the result into a 3D point, which will be returned by the function.

  • Class Cylinder

Same logic as before, but this time we start from a central point.

So, we get :

class Cylinder(Objects3D):

 

def __init__(self, object_prop, attach_point, column_bottom, column_rad, column_height):
        Objects3D.__init__(self, object_prop, attach_point, column_bottom)
        self.column_radius = column_rad
        self.column_height = column_height
        self.name_dim = f”Ø{round(column_rad / 10)}”
        self.placement_pt = self.attachment_point()

For the calculation:

  • in X,
    1. values on the left (1, 4, 7) will result in an offset equal to the radius ;
    2. values in the center (2, 5, 8) are ignored ;
    3. values on the right (3, 6, 9) induce an offset equal to the radius.

 

  • in Y
    1. values at the top (1, 2, 3) impose an offset equal to the radius ;
    2. values in the center (4, 5, 6) are ignored ;
    3. values at the bottom (7, 8, 9) will result in an offset equal to the radius.

Here is the function :

def attachment_point(self):

 

if self.attach_point in {1, 4, 7}:
        self.x_offset = self.column_radius
elif self.attach_point in {3, 6, 9}:
        self.x_offset = -self.column_radius

 

if self.attach_point in {1, 2, 3}:
        self.y_offset = -self.column_radius
elif self.attach_point in {7, 8, 9}:
        self.y_offset = self.column_radius

 

placement_pt = Geometry.Point3D(self.x_offset, self.y_offset, self.z_offset)

 

return placement_pt

  • Function create_element

Back in create_element, I complete the call to my classes with the new arguments :

 # Create 3D object
if choice == “rectangle”:
        my_column = Cuboid(com_prop, attach_point, column_bottom, column_length, column_thickness, column_height)
else:
        my_column = Cylinder(com_prop, attach_point, column_bottom, column_radius, column_height)

Next, I will perform a global translation on my PythonPart using a Matrix3D :

# Reference point
placement_mat = Geometry.Matrix3D()
placement_mat.SetTranslation(Geometry.Vector3D(my_column.placement_pt))

# Create the PythonPart
model_ele_list = pyp_util.create_pythonpart(build_ele, placement_mat)

Please note : create_pythonpart takes as its second (optional) argument a placement matrix used for the geometry transformation.

Our PythonPart is correctly affected by my translation, but there is an issue : the various handles do not follow.

We will modify them to take the offset into account :

  • Class Handle

The principle is very simple, I just have to sum my anchor points to the insertion points :

class Handle:

 

def __init__(self, placement_pt, handle_id, handle_point, ref_point, handle_param_data, handle_move_dir, handle_info_text):
        self.handle_id = handle_id
        self.handle_point = placement_pt + handle_point
        self.ref_point = placement_pt + ref_point
        self.handle_param_data = handle_param_data
        self.handle_move_dir = handle_move_dir
        self.handle_info_text = handle_info_text

I make sure to pass the anchor point during the construction of handles for the rectangular shape :

 self.handles_prop = [Handle(self.placement_pt,
                “ColumnLengthHandle”,
                Geometry.Point3D(self.column_length, 0, 0),
                Geometry.Point3D(),
                “ColumnLength“,
                HandleDirection.X_DIR,
                “Longueur”
                ),
                Handle(self.placement_pt,
                “ColumnThickHandle”,
                Geometry.Point3D(self.column_length, self.column_thick, 0),
                Geometry.Point3D(self.column_length, 0, 0),
                “ColumnThick“,
                HandleDirection.Y_DIR,
                “Largeur”
                ),
                Handle(self.placement_pt,
                “ColumnHeightHandle”,
                Geometry.Point3D(0, 0,  self.column_height),
                Geometry.Point3D(),
                “ColumnHeight“,
                HandleDirection.Z_DIR,
                “Hauteur”
                )
]

but also for the circular shape :

 self.handles_prop = [Handle(self.placement_pt,
                “ColumnRadiusHandle”,
                Geometry.Point3D(self.column_radius, 0, 0),
                Geometry.Point3D(),
                “ColumnRadius“,
                HandleDirection.X_DIR,
                “Rayon”
                ),
                Handle(self.placement_pt,
                “ColumnHeightHandle”,
                Geometry.Point3D(0, 0, self.column_height),
                Geometry.Point3D(),
                “ColumnHeight“,
                HandleDirection.Z_DIR,
                “Hauteur”
                )
]

Here is the complete file :

In this chapter, we have seen how to set up our insertion point and anchoring at different levels, greatly improving the usability of our PythonPart object.

0 Comments

Submit a Comment

Objects3D V1.0

New series in the PythonParts learning journey, let's delve into the modeling of a 3D object : a reinforced concrete column.1) GUI ScriptIn this example, we will set up the initial fields required...

Objects2D V3.0

Last step for this PythonParts example, I'll show you how to set up a legend for our object. This should include the following information : my object's name ; the name of a characteristic geometric...

Objects2D V2.0

In the previous article, we saw how to prepare our code in OOP, today let's see how to exploit its potential with this new example. Indeed, we are going to complete our PythonPart in order to...

Objects2D V1.0

We will build more complex objects on Allplan software, but first at all a word about Object Oriented Programming (OOP)...1) Object Oriented ProgrammingObject Oriented Programming (called OOP) is a...

HelloWorld V3.0

Last step for our HelloWorld script, we will see how to customize the rendering of our object.1) GUI ScriptBack in our palette, I first create a chapter to dissociate the geometric controls from...

HelloWorld V2.0

Previously, we learned how to create an object (a line with fixed length) via the PythonParts API. Today I'm going to show you how to set up handles for our HelloWorld script...1) GUI ScriptThe...

HelloWorld V1.0

HelloWorld is traditionally written to provide an example of a programming language. We will be no exception here with our first script's writing. The goal is simple, create a line with fixed...

Structure of PythonParts

Allplan is installed, your IDE is ready... perfect, let's see in detail how PythonParts work.1) Files' DescriptionTo work, a PythonPart needs at least 2 files : GUI File The interface file...

Introduction

In this series of articles, we'll study the editing of scripts in the Python programming language for the Allplan software.To allow you to follow these tutorials properly, I'm going to make a few...