Steel Hall#
|
Modelling a parametric steel hall using manipulation functions such as
Keywords:
steel hall portal frame manipulation mirror move rotate numbering increments purlins bracing |
from dlubal.api import rfem, common
import math
# -------------------------------------------------------
# This example demonstrates how to model a parametric steel
# hall with the help of manipulation functions like move,
# mirror or rotate including the copy option. The idea is to reduce
# manual modeling by leveraging symmetry and transformations.
#
# The tapering of the rafter is defined before the mirroring, so
# the mirrored rafter keeps its haunch at the eaves - the mirroring
# turns the tapering at the start of the member into a tapering at
# its end together with the reversed line orientation.
#
# The replication of the frames uses 'numbering_increments' to control
# the numbers of the created copies. The numbers of the k-th copy are
# '<original number> + k * <increment>', so every frame owns its own
# block of numbers and the purlins and bracing modelled afterwards
# derive the nodes of the neighbouring frame straight from the
# increment instead of counting the objects per frame.
# -------------------------------------------------------
# Editable parameters (SI units)
FRAME_SPACING = 5.0
FRAME_COUNT = 5
FRAME_WIDTH = 11.0
FRAME_HEIGHT_MIN = 4.5
FRAME_HEIGHT_MAX = 6.0
RAFTER_SLOPE_LENGTH = 0.75
PURLIN_FIELDS = 4
PURLIN_OFFSET_ON_TOP = 0.25
STEEL_GRADE = "S235 | EN 10025-2:2004-11"
CROSS_SECTION_COLUMN = "IPE 300"
CROSS_SECTION_RAFTER = "I 300/110/6/14/0/0/H"
CROSS_SECTION_RAFTER_TAPERED = "I 600/110/6/14/0/0/H"
CROSS_SECTION_PURLINS = "CHC 139.7x8.0"
CROSS_SECTION_BRACING = "R 20"
# Editable numbering increment of the copied frames. It has to be larger
# than the highest number used inside one frame, otherwise a copy would
# land on a number that is already taken and RFEM refuses the whole move
# with "Object 'Node No. ...' already exists".
NUMBERING_INCREMENT_FRAME = 100
# Editable first numbers of the object groups modelled after the frames
NUMBER_FIRST_PURLIN = 1001
NUMBER_FIRST_BRACING = 2001
MODEL_NAME = 'steel_hall'
def build_half_portal_frame() -> list:
inf = float('inf')
portal_frame = [
rfem.structure_core.Material(no=1, name=STEEL_GRADE),
rfem.structure_core.CrossSection(no=1, name=CROSS_SECTION_COLUMN, material=1),
rfem.structure_core.CrossSection(no=2, name=CROSS_SECTION_RAFTER, material=1),
rfem.structure_core.CrossSection(no=3, name=CROSS_SECTION_RAFTER_TAPERED, material=1),
]
portal_frame.append(rfem.structure_core.Node(no=1))
portal_frame.append(rfem.structure_core.Node(no=2, coordinate_3=-FRAME_HEIGHT_MIN))
node_no = 3
rafter_length = math.sqrt((FRAME_HEIGHT_MAX - FRAME_HEIGHT_MIN)**2 + (FRAME_WIDTH / 2)**2)
purlin_offset = (rafter_length - PURLIN_OFFSET_ON_TOP) / PURLIN_FIELDS
for j in range(1, PURLIN_FIELDS+1):
portal_frame.append(
rfem.structure_core.Node(
no=node_no,
type=rfem.structure_core.Node.TYPE_ON_MEMBER,
on_member_reference_member=2,
distance_from_start_is_defined_as_relative=False,
distance_from_start_absolute=purlin_offset*j,
)
)
node_no += 1
portal_frame.append(rfem.structure_core.Node(no=node_no, coordinate_1=FRAME_WIDTH/2, coordinate_3=-FRAME_HEIGHT_MAX))
# Lines
portal_frame.append(rfem.structure_core.Line(no=1, definition_nodes=[1, 2]))
portal_frame.append(rfem.structure_core.Line(no=2, definition_nodes=[2, node_no]))
# Members
portal_frame.append(rfem.structure_core.Member(
no=1, line=1, cross_section_start=1,
type=rfem.structure_core.Member.TYPE_BEAM))
portal_frame.append(rfem.structure_core.Member(
no=2, line=2, cross_section_start=3,
type=rfem.structure_core.Member.TYPE_BEAM,
))
# Nodal support (hinge)
portal_frame.append(rfem.types_for_nodes.NodalSupport(
no=1, nodes=[1],
spring_x=inf, spring_y=inf, spring_z=inf,
rotational_restraint_z=inf
))
return portal_frame
def enhance_rafter_with_tapering() -> list:
# The rafter is modelled from the eaves to the ridge, so the haunch
# is a tapering at the start of the member
return [
rfem.structure_core.Member(
no=2,
cross_section_end=2,
cross_section_distribution_type=rfem.structure_core.Member.CROSS_SECTION_DISTRIBUTION_TYPE_TAPERED_AT_START_OF_MEMBER,
cross_section_distance_from_start_is_defined_as_relative=False,
section_distance_from_start_absolute=RAFTER_SLOPE_LENGTH
)
]
def build_purlins_in_first_bay_half() -> list:
purlins = [
rfem.structure_core.CrossSection(no=4, name=CROSS_SECTION_PURLINS, material=1),
]
for purlin in range(1, PURLIN_FIELDS+2):
purlin_no = NUMBER_FIRST_PURLIN + (purlin - 1)
# Eaves node and the nodes on the rafter of the first frame, the
# second frame repeats them shifted by the frame increment
purlin_node_start = 2 + (purlin - 1)
purlin_node_end = purlin_node_start + NUMBERING_INCREMENT_FRAME
purlins.append(rfem.structure_core.Line(
no=purlin_no, definition_nodes=[purlin_node_start, purlin_node_end])
)
purlins.append(rfem.structure_core.Member(
no=purlin_no, line=purlin_no, cross_section_start=4,
type=rfem.structure_core.Member.TYPE_TRUSS)
)
return purlins
def build_bracing_in_first_bay_half() -> list:
bracing = [
rfem.structure_core.CrossSection(no=5, name=CROSS_SECTION_BRACING, material=1)
]
for i in range(1, PURLIN_FIELDS + 2):
for j in range(2):
bracing_no = NUMBER_FIRST_BRACING + 2*(i - 1) + j
# Diagonals of one bracing field, crossing between the first
# and the second frame
bracing_node_start = i + j
bracing_node_end = (i - j + 1) + NUMBERING_INCREMENT_FRAME
bracing.append(rfem.structure_core.Line(
no=bracing_no, definition_nodes=[bracing_node_start, bracing_node_end]
))
bracing.append(rfem.structure_core.Member(
no=bracing_no, line=bracing_no, cross_section_start=5,
type=rfem.structure_core.Member.TYPE_TRUSS_ONLY_N
))
return bracing
def get_members_by_type(rfem_app, member_type) -> list:
member_list = rfem_app.get_object_list(
objs=[
rfem.structure_core.Member()
]
)
member_list_filtered = []
for member in member_list:
# == not is: these enum values are plain ints, so identity only
# happens to hold while they stay inside CPython's small-int cache.
if member.type == member_type:
member_list_filtered.append(member)
return member_list_filtered
# --- MAIN SCRIPT ---
# Connect to the RFEM application
with rfem.Application() as rfem_app:
rfem_app.close_all_models(save_changes=False)
rfem_app.create_model(name=MODEL_NAME)
rfem_app.delete_all_objects()
# Build the half of the portal frame
portal_frame = build_half_portal_frame()
rfem_app.create_object_list(
objs=portal_frame
)
# Enhance the rafter with tapering before mirroring, the mirrored
# rafter then gets its haunch at the eaves as well
tapered_rafter = enhance_rafter_with_tapering()
rfem_app.update_object_list(
objs=tapered_rafter
)
# Mirror the half of the portal frame as copy to its mirrored position
rfem_app.mirror_objects(
objs=portal_frame,
mirroring_type=rfem.manipulation.MIRRORING_PLANE_SPECIFICATION_TYPE_POINT_AND_PARALLEL_PLANE,
plane=rfem.manipulation.PLANE_YZ,
point_1=common.Vector3d(x=FRAME_WIDTH/2,y=0,z=-FRAME_HEIGHT_MAX),
create_copy=True,
)
# Move the frame to create multiple frames by applying a vector displacement.
# Every frame is numbered from 1 + <frame index> * NUMBERING_INCREMENT_FRAME,
# which the purlins and the bracing rely on below.
rfem_app.move_objects(
objs=rfem_app.get_object_list(
objs=[
rfem.structure_core.Node(),
rfem.structure_core.Member(),
]
),
create_copy=True,
number_of_steps=FRAME_COUNT-1,
direction_through=rfem.manipulation.DIRECTION_THROUGH_DISPLACEMENT_VECTOR,
displacement_vector=common.Vector3d(x=0.0, y=FRAME_SPACING, z=0.0),
numbering_increments={
rfem.OBJECT_TYPE_NODE: NUMBERING_INCREMENT_FRAME,
rfem.OBJECT_TYPE_LINE: NUMBERING_INCREMENT_FRAME,
rfem.OBJECT_TYPE_MEMBER: NUMBERING_INCREMENT_FRAME,
},
)
# Model purlins between the first two frames, on one symmetric half of the hall
purlins = build_purlins_in_first_bay_half()
rfem_app.create_object_list(objs=purlins)
# Rotate purlins to symmetric half of the hall
rfem_app.rotate_objects(
objs=purlins,
rotation_angle=math.pi - 2*math.atan((FRAME_HEIGHT_MAX - FRAME_HEIGHT_MIN) / (FRAME_WIDTH / 2)),
point_1=common.Vector3d(x=FRAME_WIDTH/2,y=0,z=-FRAME_HEIGHT_MAX),
axis=rfem.manipulation.COORDINATE_AXIS_Y,
rotation_axis=rfem.manipulation.ROTATION_AXIS_SPECIFICATION_TYPE_POINT_AND_PARALLEL_AXIS,
create_copy=True,
number_of_steps=1
)
# Copy the purlins parallel to the axis for the remaining bays
rfem_app.move_objects(
objs=get_members_by_type(
rfem_app, rfem.structure_core.Member.TYPE_TRUSS
),
create_copy=True,
number_of_steps=FRAME_COUNT-2,
direction_through=rfem.manipulation.DIRECTION_THROUGH_PARALLEL_TO_AXIS,
axis=rfem.manipulation.COORDINATE_AXIS_Y,
spacing=FRAME_SPACING
)
# Model bracing between the first two frames, on one symmetric half of the hall
bracing = build_bracing_in_first_bay_half()
rfem_app.create_object_list(objs=bracing)
# Mirror the bracing to symmetric half of the hall
rfem_app.rotate_objects(
objs=get_members_by_type(
rfem_app, rfem.structure_core.Member.TYPE_TRUSS_ONLY_N
),
rotation_angle=math.pi,
point_1=common.Vector3d(x=FRAME_WIDTH/2,y=FRAME_SPACING/2,z=-FRAME_HEIGHT_MAX),
axis=rfem.manipulation.COORDINATE_AXIS_Z,
rotation_axis=rfem.manipulation.ROTATION_AXIS_SPECIFICATION_TYPE_POINT_AND_PARALLEL_AXIS,
create_copy=True,
number_of_steps=1
)
/**
Parametric steel hall modelled with the manipulation functions.
This example demonstrates how to model a parametric steel hall with the help of
manipulation functions like move, mirror or rotate including the copy option.
The idea is to reduce manual modelling by leveraging symmetry and transformations.
The tapering of the rafter is defined before the mirroring, so the mirrored
rafter keeps its haunch at the eaves - the mirroring turns the tapering at the
start of the member into a tapering at its end together with the reversed line
orientation.
The replication of the frames uses 'numberingIncrements' to control the numbers
of the created copies. The numbers of the k-th copy are
'<original number> + k * <increment>', so every frame owns its own block of
numbers and the purlins and bracing modelled afterwards derive the nodes of the
neighbouring frame straight from the increment instead of counting the objects
per frame.
**/
using Rfem = Dlubal.Api.Rfem;
using Common = Dlubal.Api.Common;
using Google.Protobuf;
// Editable parameters (SI units)
const double FRAME_SPACING = 5.0;
const int FRAME_COUNT = 5;
const double FRAME_WIDTH = 11.0;
const double FRAME_HEIGHT_MIN = 4.5;
const double FRAME_HEIGHT_MAX = 6.0;
const double RAFTER_SLOPE_LENGTH = 0.75;
const int PURLIN_FIELDS = 4;
const double PURLIN_OFFSET_ON_TOP = 0.25;
const string STEEL_GRADE = "S235 | EN 10025-2:2004-11";
const string CROSS_SECTION_COLUMN = "IPE 300";
const string CROSS_SECTION_RAFTER = "I 300/110/6/14/0/0/H";
const string MODEL_NAME = "steel_hall";
const string CROSS_SECTION_RAFTER_TAPERED = "I 600/110/6/14/0/0/H";
const string CROSS_SECTION_PURLIN = "CHC 139.7x8.0";
const string CROSS_SECTION_BRACING = "R 20";
// Editable numbering increment of the copied frames. It has to be larger than
// the highest number used inside one frame, otherwise a copy would land on a
// number that is already taken and the whole move is refused with
// "Object 'Node No. ...' already exists".
const int NUMBERING_INCREMENT_FRAME = 100;
// Editable first numbers of the object groups modelled after the frames
const int NUMBER_FIRST_PURLIN = 1001;
const int NUMBER_FIRST_BRACING = 2001;
static List<IMessage> BuildHalfPortalFrame()
{
double inf = double.PositiveInfinity;
var portalFrame = new List<IMessage>
{
// Material
new Rfem.StructureCore.Material{No=1, Name=STEEL_GRADE},
// Cross-section
new Rfem.StructureCore.CrossSection{No=1, Name=CROSS_SECTION_COLUMN, Material=1},
new Rfem.StructureCore.CrossSection{No=2, Name=CROSS_SECTION_RAFTER, Material=1},
new Rfem.StructureCore.CrossSection{No=3, Name=CROSS_SECTION_RAFTER_TAPERED, Material=1},
};
// Nodes
portalFrame.Add(new Rfem.StructureCore.Node{No=1});
portalFrame.Add(new Rfem.StructureCore.Node{No=2, Coordinate3=-FRAME_HEIGHT_MIN});
var nodeNo = 3;
var rafterLength = Math.Sqrt(Math.Pow(FRAME_HEIGHT_MAX - FRAME_HEIGHT_MIN, 2) + Math.Pow(FRAME_WIDTH / 2, 2));
var purlinOffset = (rafterLength - PURLIN_OFFSET_ON_TOP) / PURLIN_FIELDS;
for (int j = 1; j <= PURLIN_FIELDS; j++)
{
var node = new Rfem.StructureCore.Node
{
No = nodeNo,
Type = Rfem.StructureCore.Node.Types.Type.OnMember,
OnMemberReferenceMember = 2,
DistanceFromStartIsDefinedAsRelative = false,
DistanceFromStartAbsolute = purlinOffset * j
};
portalFrame.Add(node);
nodeNo++;
}
portalFrame.Add(new Rfem.StructureCore.Node{No=nodeNo, Coordinate1=FRAME_WIDTH/2, Coordinate3=-FRAME_HEIGHT_MAX});
// Lines
portalFrame.Add(new Rfem.StructureCore.Line{No=1, DefinitionNodes={1, 2}});
portalFrame.Add(new Rfem.StructureCore.Line{No=2, DefinitionNodes={2, nodeNo}});
// Members
portalFrame.Add(new Rfem.StructureCore.Member{
No=1, Line=1, CrossSectionStart=1,
Type=Rfem.StructureCore.Member.Types.Type.Beam
});
portalFrame.Add(new Rfem.StructureCore.Member{
No=2, Line=2, CrossSectionStart=3,
Type=Rfem.StructureCore.Member.Types.Type.Beam
});
// Nodal support (hinge)
portalFrame.Add(new Rfem.TypesForNodes.NodalSupport{
No=1, Nodes={ 1 },
SpringX=inf, SpringY=inf, SpringZ=inf,
RotationalRestraintZ=inf
});
return portalFrame;
}
static List<IMessage> EnhanceRafterWithTapering()
{
// The rafter is modelled from the eaves to the ridge, so the haunch is a
// tapering at the start of the member
return new List<IMessage>
{
new Rfem.StructureCore.Member
{
No = 2,
CrossSectionEnd = 2,
CrossSectionDistributionType = Rfem.StructureCore.Member.Types.CrossSectionDistributionType.TaperedAtStartOfMember,
CrossSectionDistanceFromStartIsDefinedAsRelative = false,
SectionDistanceFromStartAbsolute = RAFTER_SLOPE_LENGTH
}
};
}
static List<IMessage> BuildPurlinsInFirstBayHalf()
{
var purlins = new List<IMessage>
{
new Rfem.StructureCore.CrossSection
{
No = 4,
Name = CROSS_SECTION_PURLIN,
Material = 1
}
};
for (int purlin = 1; purlin <= PURLIN_FIELDS + 1; purlin++)
{
int purlinNo = NUMBER_FIRST_PURLIN + (purlin - 1);
// Eaves node and the nodes on the rafter of the first frame, the second
// frame repeats them shifted by the frame increment
int purlinNodeStart = 2 + (purlin - 1);
int purlinNodeEnd = purlinNodeStart + NUMBERING_INCREMENT_FRAME;
purlins.Add(new Rfem.StructureCore.Line
{
No = purlinNo,
DefinitionNodes = { purlinNodeStart, purlinNodeEnd }
});
purlins.Add(new Rfem.StructureCore.Member
{
No = purlinNo,
Line = purlinNo,
CrossSectionStart = 4,
Type = Rfem.StructureCore.Member.Types.Type.Truss
});
}
return purlins;
}
static List<IMessage> BuildBracingInFirstBayHalf()
{
var bracing = new List<IMessage>
{
new Rfem.StructureCore.CrossSection
{
No = 5,
Name = CROSS_SECTION_BRACING,
Material = 1
}
};
for (int i = 1; i <= PURLIN_FIELDS + 1; i++)
{
for (int j = 0; j < 2; j++)
{
int bracingNo = NUMBER_FIRST_BRACING + 2 * (i - 1) + j;
// Diagonals of one bracing field, crossing between the first and
// the second frame
int bracingNodeStart = i + j;
int bracingNodeEnd = (i - j + 1) + NUMBERING_INCREMENT_FRAME;
bracing.Add(new Rfem.StructureCore.Line
{
No = bracingNo,
DefinitionNodes = { bracingNodeStart, bracingNodeEnd }
});
bracing.Add(new Rfem.StructureCore.Member
{
No = bracingNo,
Line = bracingNo,
CrossSectionStart = 5,
Type = Rfem.StructureCore.Member.Types.Type.TrussOnlyN
});
}
}
return bracing;
}
static List<IMessage> GetMembersByType(
ApplicationRfem rfemApp,
Rfem.StructureCore.Member.Types.Type memberType)
{
var memberList = rfemApp.get_object_list(
objs: new List<IMessage>{
new Rfem.StructureCore.Member{}
}
);
var memberListFiltered = new List<IMessage>();
foreach (var obj in memberList)
{
if (obj is Rfem.StructureCore.Member member &&
member.Type == memberType)
{
memberListFiltered.Add(member);
}
}
return memberListFiltered;
}
// --- MAIN SCRIPT ---
ApplicationRfem? rfemApp = null;
try
{
// Connect to the RFEM application
rfemApp = new ApplicationRfem();
rfemApp.close_all_models(saveChanges: false);
rfemApp.create_model(MODEL_NAME);
rfemApp.delete_all_objects();
// Build the half of the portal frame
var portalFrame = BuildHalfPortalFrame();
rfemApp.create_object_list(objs: portalFrame);
// Enhance the rafter with tapering before mirroring, the mirrored rafter
// then gets its haunch at the eaves as well
var taperedRafter = EnhanceRafterWithTapering();
rfemApp.update_object_list(objs: taperedRafter);
// Mirror the half of the portal frame as copy to its mirrored position
rfemApp.mirror_objects(
objs: portalFrame,
mirroringType: Rfem.Manipulation.MirroringPlaneSpecificationType.PointAndParallelPlane,
plane: Rfem.Manipulation.Plane.Yz,
point1: new Common.Vector3d{X=FRAME_WIDTH/2, Y=0, Z=-FRAME_HEIGHT_MAX},
createCopy: true
);
// Move the frame to create multiple frames by applying a vector displacement.
// Every frame is numbered from 1 + <frame index> * NUMBERING_INCREMENT_FRAME,
// which the purlins and the bracing rely on below.
rfemApp.move_objects(
objs: rfemApp.get_object_list(
objs: new List<IMessage> {
new Rfem.StructureCore.Node {},
new Rfem.StructureCore.Member {}
}
),
createCopy: true,
numberOfSteps: FRAME_COUNT - 1,
directionThrough: Rfem.Manipulation.DirectionThrough.DisplacementVector,
displacementVector: new Common.Vector3d{X=0.0, Y=FRAME_SPACING, Z=0.0},
numberingIncrements: new Dictionary<Rfem.ObjectType, int>
{
{ Rfem.ObjectType.Node, NUMBERING_INCREMENT_FRAME },
{ Rfem.ObjectType.Line, NUMBERING_INCREMENT_FRAME },
{ Rfem.ObjectType.Member, NUMBERING_INCREMENT_FRAME },
}
);
// Model purlins between the first two frames, on one symmetric half of the hall
var purlins = BuildPurlinsInFirstBayHalf();
rfemApp.create_object_list(objs: purlins);
// Rotate purlins to symmetric half of the hall
rfemApp.rotate_objects(
objs: purlins,
rotationAngle: Math.PI - 2*Math.Atan((FRAME_HEIGHT_MAX - FRAME_HEIGHT_MIN) / (FRAME_WIDTH / 2)),
point1: new Common.Vector3d{X=FRAME_WIDTH/2, Y=0, Z=-FRAME_HEIGHT_MAX},
axis: Rfem.Manipulation.CoordinateAxis.Y,
rotationAxis: Rfem.Manipulation.RotationAxisSpecificationType.PointAndParallelAxis,
createCopy: true,
numberOfSteps: 1
);
// Copy the purlins parallel to the axis for the remaining bays
rfemApp.move_objects(
objs: GetMembersByType(
rfemApp, Rfem.StructureCore.Member.Types.Type.Truss
),
createCopy: true,
numberOfSteps: FRAME_COUNT-2,
directionThrough: Rfem.Manipulation.DirectionThrough.ParallelToAxis,
axis: Rfem.Manipulation.CoordinateAxis.Y,
spacing: FRAME_SPACING
);
// Model bracing between the first two frames, on one symmetric half of the hall
var bracing = BuildBracingInFirstBayHalf();
rfemApp.create_object_list(objs: bracing);
// Mirror the bracing to symmetric half of the hall
rfemApp.rotate_objects(
objs: GetMembersByType(
rfemApp, Rfem.StructureCore.Member.Types.Type.TrussOnlyN
),
rotationAngle: Math.PI,
point1: new Common.Vector3d{X=FRAME_WIDTH/2, Y=FRAME_SPACING/2, Z=-FRAME_HEIGHT_MAX},
axis: Rfem.Manipulation.CoordinateAxis.Z,
rotationAxis: Rfem.Manipulation.RotationAxisSpecificationType.PointAndParallelAxis,
createCopy: true,
numberOfSteps: 1
);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
if (rfemApp != null) rfemApp.close_connection();
}