Square Silo#
|
Modelling a square steel silo on a braced leg tower and reading the bending moments out of its hopper:
Keywords:
square silo surfaces solid bulk material thickness member hinge rib nodal support second order surface results |
from math import inf, pi
from dlubal.api import rfem
# -------------------------------------------------------
# This example models a square steel silo on a braced leg
# tower and reads the bending moments out of its hopper
# panels.
#
# The whole model is generated from the parameters below.
# Everything is square in plan and symmetric about both
# axes, so each part is built by walking the four faces
# rather than by listing coordinates: change PLAN_SIZE or
# any level and the nodes, lines, members and surfaces
# follow.
#
# RFEM's global Z axis points DOWN, so a level h metres
# above the ground is entered as z = -h. The helper
# functions below take heights and do that conversion in
# one place.
# -------------------------------------------------------
# Editable parameters (SI units)
MODEL_NAME = 'square_silo'
# Geometry, as heights above the leg bases
PLAN_SIZE = 3.0 # square silo, outside dimension [m]
BRACING_HEIGHT = 3.0 # horizontal bracing ring, mid-height of the legs [m]
FLOOR_HEIGHT = 6.0 # top of the legs, underside of the bin [m]
ROOF_HEIGHT = 12.0 # top of the bin [m]
OUTLET_SIZE = 1.5 # square outlet, side length [m]
OUTLET_HEIGHT = 4.0 # outlet above the leg bases [m]
# Materials and sections
STEEL = "S450 | EN 1993-1-1:2005-05"
BULK_SOLID = "Sand, well-graded (SW) | DIN 18196:2011-05"
SECTION_LEG = "IPN 300"
SECTION_FLOOR_RIB = "UPE 200"
SECTION_BRACING = "MSH KHP 88.9x3.6"
SECTION_BIN_POST = "LU 300/200/10/10/0"
# Plate thicknesses
THICKNESS_HOPPER = 0.010 # [m], the hopper works hardest
THICKNESS_SHELL = 0.008 # [m], bin walls and the outlet plate
THICKNESS_ROOF = 0.005 # [m]
# Elastic foundation under each leg base
SUPPORT_SPRING_HORIZONTAL = 700000.0 # [N/m]
SUPPORT_SPRING_VERTICAL = 5000000.0 # [N/m]
# Torsional spring in the bracing connections
HINGE_TORSIONAL_STIFFNESS = 28000.0 # [Nm/rad]
# Object numbers. Each group owns a block so the generated numbers stay
# readable and the surfaces can refer to line numbers without a lookup.
N_LEG_BASE, N_BRACING, N_FLOOR, N_OUTLET, N_ROOF = 1, 5, 13, 21, 25
L_LEG_LOWER, L_LEG_UPPER, L_FLOOR_RING = 1, 5, 9
L_BRACE_LOWER, L_BRACE_UPPER, L_BRACE_RING = 17, 25, 33
L_OUTLET, L_VALLEY, L_POST, L_ROOF = 41, 45, 49, 53
FACES = range(4) # the four sides of the square, everywhere
def corner(k, size, height):
"""Corner k of a square of the given size, centred in plan on PLAN_SIZE."""
inset = (PLAN_SIZE - size) / 2
a, b = inset, PLAN_SIZE - inset
x, y = [(a, a), (b, a), (b, b), (a, b)][k % 4]
return x, y, -height
def ring_point(i, height):
"""Point i of the eight-point perimeter: corner, edge midpoint, corner, ...
Even i are the corners, odd i the midpoints the bracing runs up to.
"""
if i % 2 == 0:
return corner(i // 2, PLAN_SIZE, height)
x0, y0, _ = corner(i // 2, PLAN_SIZE, height)
x1, y1, _ = corner(i // 2 + 1, PLAN_SIZE, height)
return (x0 + x1) / 2, (y0 + y1) / 2, -height
def node(no, point):
x, y, z = point
return rfem.structure_core.Node(no=no, coordinate_1=x, coordinate_2=y, coordinate_3=z)
def line(no, start, end):
return rfem.structure_core.Line(no=no, definition_nodes=[start, end])
def define_structure() -> list:
"""Materials, sections, plate thicknesses and the whole geometry."""
objects = [
rfem.structure_core.Material(no=1, name=STEEL),
rfem.structure_core.Material(no=2, name=BULK_SOLID),
rfem.structure_core.CrossSection(no=1, material=1, name=SECTION_LEG),
rfem.structure_core.CrossSection(no=2, material=1, name=SECTION_FLOOR_RIB),
rfem.structure_core.CrossSection(no=3, material=1, name=SECTION_BRACING),
rfem.structure_core.CrossSection(no=4, material=1, name=SECTION_BIN_POST),
# Torsional spring at both ends of every braced member. The second
# hinge also frees minor-axis bending, which is what makes the bracing
# act as a strut rather than a frame member.
rfem.types_for_members.MemberHinge(
no=1,
axial_release_n=inf, axial_release_vy=inf, axial_release_vz=inf,
moment_release_mt=HINGE_TORSIONAL_STIFFNESS),
rfem.types_for_members.MemberHinge(
no=2,
axial_release_n=inf, axial_release_vy=inf, axial_release_vz=inf,
moment_release_mt=HINGE_TORSIONAL_STIFFNESS, moment_release_mz=inf),
]
# --- nodes ---------------------------------------------------------------
objects += [node(N_LEG_BASE + k, corner(k, PLAN_SIZE, 0.0)) for k in FACES]
objects += [node(N_BRACING + i, ring_point(i, BRACING_HEIGHT)) for i in range(8)]
objects += [node(N_FLOOR + i, ring_point(i, FLOOR_HEIGHT)) for i in range(8)]
objects += [node(N_OUTLET + k, corner(k, OUTLET_SIZE, OUTLET_HEIGHT)) for k in FACES]
objects += [node(N_ROOF + k, corner(k, PLAN_SIZE, ROOF_HEIGHT)) for k in FACES]
# --- lines ---------------------------------------------------------------
# Legs, in two lifts so the bracing ring has something to frame into.
objects += [line(L_LEG_LOWER + k, N_LEG_BASE + k, N_BRACING + 2 * k) for k in FACES]
objects += [line(L_LEG_UPPER + k, N_BRACING + 2 * k, N_FLOOR + 2 * k) for k in FACES]
# The eight floor-ring segments, which are also the bin wall and hopper edges.
objects += [line(L_FLOOR_RING + i, N_FLOOR + i, N_FLOOR + (i + 1) % 8) for i in range(8)]
# Vertical bracing: a V per face, springing from the two corners up to the
# edge midpoint of the level above.
for f in FACES:
objects += [
line(L_BRACE_LOWER + 2 * f, N_LEG_BASE + f, N_BRACING + 2 * f + 1),
line(L_BRACE_LOWER + 2 * f + 1, N_LEG_BASE + (f + 1) % 4, N_BRACING + 2 * f + 1),
line(L_BRACE_UPPER + 2 * f, N_BRACING + 2 * f, N_FLOOR + 2 * f + 1),
line(L_BRACE_UPPER + 2 * f + 1, N_BRACING + 2 * ((f + 1) % 4), N_FLOOR + 2 * f + 1),
]
objects += [line(L_BRACE_RING + i, N_BRACING + i, N_BRACING + (i + 1) % 8) for i in range(8)]
# Hopper, bin posts and roof.
objects += [line(L_OUTLET + k, N_OUTLET + k, N_OUTLET + (k + 1) % 4) for k in FACES]
objects += [line(L_VALLEY + k, N_FLOOR + 2 * k, N_OUTLET + k) for k in FACES]
objects += [line(L_POST + k, N_FLOOR + 2 * k, N_ROOF + k) for k in FACES]
objects += [line(L_ROOF + k, N_ROOF + k, N_ROOF + (k + 1) % 4) for k in FACES]
# --- members -------------------------------------------------------------
braced = dict(member_hinge_start=1, member_hinge_end=2)
no = 1
for k in FACES: # legs, lower lift
objects.append(rfem.structure_core.Member(
no=no, line=L_LEG_LOWER + k, cross_section_start=1, **braced)); no += 1
for k in FACES: # legs, upper lift
objects.append(rfem.structure_core.Member(
no=no, line=L_LEG_UPPER + k, cross_section_start=1, **braced)); no += 1
for i in range(8): # floor ring, ribs in the plate
objects.append(rfem.structure_core.Member(
no=no, line=L_FLOOR_RING + i, cross_section_start=2,
type=rfem.structure_core.Member.TYPE_RIB)); no += 1
for i in range(16): # vertical bracing
objects.append(rfem.structure_core.Member(
no=no, line=L_BRACE_LOWER + i, cross_section_start=3, **braced)); no += 1
for i in range(8): # horizontal bracing ring
# The torsional hinge sits at the CORNER end of each segment, so it
# alternates between the start and the end as the ring is walked.
hinge = dict(member_hinge_start=1) if i % 2 == 0 else dict(member_hinge_end=2)
objects.append(rfem.structure_core.Member(
no=no, line=L_BRACE_RING + i, cross_section_start=3, **hinge)); no += 1
for k in FACES: # bin corner posts
# The channel has to open towards the inside of the bin, so each post is
# turned a further quarter turn as the corners are walked.
objects.append(rfem.structure_core.Member(
no=no, line=L_POST + k, cross_section_start=4,
rotation_angle=pi / 2 - k * pi / 2)); no += 1
# --- surfaces ------------------------------------------------------------
# Outlet plate, then the four hopper panels, the four bin walls, the bin
# floor and the roof. Each panel is closed by walking its own face.
objects.append(rfem.structure_core.Surface(
no=1, boundary_lines=[L_OUTLET + k for k in FACES]))
for f in FACES:
objects.append(rfem.structure_core.Surface(
no=2 + f,
boundary_lines=[L_VALLEY + f, L_OUTLET + f, L_VALLEY + (f + 1) % 4,
L_FLOOR_RING + 2 * f + 1, L_FLOOR_RING + 2 * f]))
for f in FACES:
objects.append(rfem.structure_core.Surface(
no=6 + f,
boundary_lines=[L_FLOOR_RING + 2 * f, L_FLOOR_RING + 2 * f + 1,
L_POST + (f + 1) % 4, L_ROOF + f, L_POST + f]))
# The bin floor closes the stored solid from above. It is the free surface
# of the bulk material, not a steel plate, so it carries no thickness.
objects.append(rfem.structure_core.Surface(
no=10, boundary_lines=[L_FLOOR_RING + i for i in range(8)],
type=rfem.structure_core.Surface.TYPE_WITHOUT_THICKNESS))
objects.append(rfem.structure_core.Surface(
no=11, boundary_lines=[L_ROOF + k for k in FACES]))
# Assigned AFTER the surfaces exist. A Thickness created first is accepted
# but its assigned_to_surfaces is dropped, and every surface then silently
# falls back to thickness 1 - which is how this model used to run with the
# hopper's 10 mm plate everywhere instead of 10 / 8 / 5 mm.
objects += [
rfem.structure_core.Thickness(
no=1, material=1, uniform_thickness=THICKNESS_HOPPER,
assigned_to_surfaces=[2, 3, 4, 5]),
rfem.structure_core.Thickness(
no=2, material=1, uniform_thickness=THICKNESS_SHELL,
assigned_to_surfaces=[1, 6, 7, 8, 9]),
rfem.structure_core.Thickness(
no=3, material=1, uniform_thickness=THICKNESS_ROOF,
assigned_to_surfaces=[11]),
]
objects += [
# The stored solid, bounded by the hopper, the outlet plate and the bin
# floor. Its self weight is what loads the hopper panels.
rfem.structure_core.Solid(
no=1, type=rfem.structure_core.Solid.TYPE_STANDARD, material=2,
boundary_surfaces=[1, 2, 3, 4, 5, 10]),
# Every leg base on the same elastic foundation.
rfem.types_for_nodes.NodalSupport(
no=1,
nodes=[N_LEG_BASE + k for k in FACES],
spring_x=SUPPORT_SPRING_HORIZONTAL,
spring_y=SUPPORT_SPRING_HORIZONTAL,
spring_z=SUPPORT_SPRING_VERTICAL),
]
return objects
def define_loading() -> list:
"""Analysis settings and load cases."""
return [
rfem.loading.StaticAnalysisSettings(no=1),
rfem.loading.StaticAnalysisSettings(
no=2,
analysis_type=rfem.loading.StaticAnalysisSettings.ANALYSIS_TYPE_SECOND_ORDER_P_DELTA,
number_of_load_increments=2),
rfem.loading.LoadCase(
no=1, name="Self weight", static_analysis_settings=1,
self_weight_active=True),
rfem.loading.LoadCase(
no=2, name="Stability - second order", static_analysis_settings=2,
action_category=rfem.loading.LoadCase.ACTION_CATEGORY_PERMANENT_IMPOSED_GQ,
self_weight_active=True),
]
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()
rfem_app.create_object_list(define_structure() + define_loading())
rfem_app.calculate_all(skip_warnings=True)
# Results: bending in the four hopper panels, which carry the stored solid.
hopper_panels = rfem.results.ResultsFilter(
column_id='surface_no',
filter_expression=', '.join(str(2 + f) for f in FACES),
)
print("\nHopper panels | Basic internal forces at grid points:")
print(rfem_app.get_results(
results_type=rfem.results.STATIC_ANALYSIS_SURFACES_BASIC_INTERNAL_FORCES_GRID_POINTS,
filters=[hopper_panels],
).data)
print("\nHopper panels | Basic internal forces at mesh nodes:")
print(rfem_app.get_results(
results_type=rfem.results.STATIC_ANALYSIS_SURFACES_BASIC_INTERNAL_FORCES_MESH_NODES,
filters=[hopper_panels],
).data)
using Rfem = Dlubal.Api.Rfem;
using Dlubal.Api.Common;
using Google.Protobuf;
// -------------------------------------------------------
// This example models a square steel silo on a braced leg
// tower and reads the bending moments out of its hopper
// panels.
//
// The whole model is generated from the parameters below.
// Everything is square in plan and symmetric about both
// axes, so each part is built by walking the four faces
// rather than by listing coordinates: change PLAN_SIZE or
// any level and the nodes, lines, members and surfaces
// follow.
//
// RFEM's global Z axis points DOWN, so a level h metres
// above the ground is entered as z = -h. The helper
// functions below take heights and do that conversion in
// one place.
// -------------------------------------------------------
// Editable parameters (SI units)
const string MODEL_NAME = "square_silo";
// Geometry, as heights above the leg bases
const double PLAN_SIZE = 3.0; // square silo, outside dimension [m]
const double BRACING_HEIGHT = 3.0; // horizontal bracing ring, mid-height of the legs [m]
const double FLOOR_HEIGHT = 6.0; // top of the legs, underside of the bin [m]
const double ROOF_HEIGHT = 12.0; // top of the bin [m]
const double OUTLET_SIZE = 1.5; // square outlet, side length [m]
const double OUTLET_HEIGHT = 4.0; // outlet above the leg bases [m]
// Materials and sections
const string STEEL = "S450 | EN 1993-1-1:2005-05";
const string BULK_SOLID = "Sand, well-graded (SW) | DIN 18196:2011-05";
const string SECTION_LEG = "IPN 300";
const string SECTION_FLOOR_RIB = "UPE 200";
const string SECTION_BRACING = "MSH KHP 88.9x3.6";
const string SECTION_BIN_POST = "LU 300/200/10/10/0";
// Plate thicknesses
const double THICKNESS_HOPPER = 0.010; // [m], the hopper works hardest
const double THICKNESS_SHELL = 0.008; // [m], bin walls and the outlet plate
const double THICKNESS_ROOF = 0.005; // [m]
// Elastic foundation under each leg base
const double SUPPORT_SPRING_HORIZONTAL = 700000.0; // [N/m]
const double SUPPORT_SPRING_VERTICAL = 5000000.0; // [N/m]
// Torsional spring in the bracing connections
const double HINGE_TORSIONAL_STIFFNESS = 28000.0; // [Nm/rad]
// Object numbers. Each group owns a block so the generated numbers stay
// readable and the surfaces can refer to line numbers without a lookup.
const int N_LEG_BASE = 1, N_BRACING = 5, N_FLOOR = 13, N_OUTLET = 21, N_ROOF = 25;
const int L_LEG_LOWER = 1, L_LEG_UPPER = 5, L_FLOOR_RING = 9;
const int L_BRACE_LOWER = 17, L_BRACE_UPPER = 25, L_BRACE_RING = 33;
const int L_OUTLET = 41, L_VALLEY = 45, L_POST = 49, L_ROOF = 53;
// Corner k of a square of the given size, centred in plan on PLAN_SIZE.
static (double X, double Y, double Z) Corner(int k, double size, double height)
{
double inset = (PLAN_SIZE - size) / 2.0;
double a = inset, b = PLAN_SIZE - inset;
var plan = new (double, double)[] { (a, a), (b, a), (b, b), (a, b) }[((k % 4) + 4) % 4];
return (plan.Item1, plan.Item2, -height);
}
// Point i of the eight-point perimeter: corner, edge midpoint, corner, ...
// Even i are the corners, odd i the midpoints the bracing runs up to.
static (double X, double Y, double Z) RingPoint(int i, double height)
{
if (i % 2 == 0) return Corner(i / 2, PLAN_SIZE, height);
var p0 = Corner(i / 2, PLAN_SIZE, height);
var p1 = Corner(i / 2 + 1, PLAN_SIZE, height);
return ((p0.X + p1.X) / 2.0, (p0.Y + p1.Y) / 2.0, -height);
}
static IMessage Node(int no, (double X, double Y, double Z) p) =>
new Rfem.StructureCore.Node { No = no, Coordinate1 = p.X, Coordinate2 = p.Y, Coordinate3 = p.Z };
static IMessage Line(int no, int start, int end) =>
new Rfem.StructureCore.Line { No = no, DefinitionNodes = { start, end } };
// Materials, sections, plate thicknesses and the whole geometry.
static List<IMessage> DefineStructure()
{
var objects = new List<IMessage>
{
new Rfem.StructureCore.Material { No = 1, Name = STEEL },
new Rfem.StructureCore.Material { No = 2, Name = BULK_SOLID },
new Rfem.StructureCore.CrossSection { No = 1, Material = 1, Name = SECTION_LEG },
new Rfem.StructureCore.CrossSection { No = 2, Material = 1, Name = SECTION_FLOOR_RIB },
new Rfem.StructureCore.CrossSection { No = 3, Material = 1, Name = SECTION_BRACING },
new Rfem.StructureCore.CrossSection { No = 4, Material = 1, Name = SECTION_BIN_POST },
// Torsional spring at both ends of every braced member. The second
// hinge also frees minor-axis bending, which is what makes the bracing
// act as a strut rather than a frame member.
new Rfem.TypesForMembers.MemberHinge {
No = 1,
AxialReleaseN = double.PositiveInfinity,
AxialReleaseVy = double.PositiveInfinity,
AxialReleaseVz = double.PositiveInfinity,
MomentReleaseMt = HINGE_TORSIONAL_STIFFNESS
},
new Rfem.TypesForMembers.MemberHinge {
No = 2,
AxialReleaseN = double.PositiveInfinity,
AxialReleaseVy = double.PositiveInfinity,
AxialReleaseVz = double.PositiveInfinity,
MomentReleaseMt = HINGE_TORSIONAL_STIFFNESS,
MomentReleaseMz = double.PositiveInfinity
},
};
// --- nodes ---------------------------------------------------------------
for (int k = 0; k < 4; k++) objects.Add(Node(N_LEG_BASE + k, Corner(k, PLAN_SIZE, 0.0)));
for (int i = 0; i < 8; i++) objects.Add(Node(N_BRACING + i, RingPoint(i, BRACING_HEIGHT)));
for (int i = 0; i < 8; i++) objects.Add(Node(N_FLOOR + i, RingPoint(i, FLOOR_HEIGHT)));
for (int k = 0; k < 4; k++) objects.Add(Node(N_OUTLET + k, Corner(k, OUTLET_SIZE, OUTLET_HEIGHT)));
for (int k = 0; k < 4; k++) objects.Add(Node(N_ROOF + k, Corner(k, PLAN_SIZE, ROOF_HEIGHT)));
// --- lines ---------------------------------------------------------------
// Legs, in two lifts so the bracing ring has something to frame into.
for (int k = 0; k < 4; k++) objects.Add(Line(L_LEG_LOWER + k, N_LEG_BASE + k, N_BRACING + 2 * k));
for (int k = 0; k < 4; k++) objects.Add(Line(L_LEG_UPPER + k, N_BRACING + 2 * k, N_FLOOR + 2 * k));
// The eight floor-ring segments, which are also the bin wall and hopper edges.
for (int i = 0; i < 8; i++) objects.Add(Line(L_FLOOR_RING + i, N_FLOOR + i, N_FLOOR + (i + 1) % 8));
// Vertical bracing: a V per face, springing from the two corners up to the
// edge midpoint of the level above.
for (int f = 0; f < 4; f++)
{
objects.Add(Line(L_BRACE_LOWER + 2 * f, N_LEG_BASE + f, N_BRACING + 2 * f + 1));
objects.Add(Line(L_BRACE_LOWER + 2 * f + 1, N_LEG_BASE + (f + 1) % 4, N_BRACING + 2 * f + 1));
objects.Add(Line(L_BRACE_UPPER + 2 * f, N_BRACING + 2 * f, N_FLOOR + 2 * f + 1));
objects.Add(Line(L_BRACE_UPPER + 2 * f + 1, N_BRACING + 2 * ((f + 1) % 4), N_FLOOR + 2 * f + 1));
}
for (int i = 0; i < 8; i++) objects.Add(Line(L_BRACE_RING + i, N_BRACING + i, N_BRACING + (i + 1) % 8));
// Hopper, bin posts and roof.
for (int k = 0; k < 4; k++) objects.Add(Line(L_OUTLET + k, N_OUTLET + k, N_OUTLET + (k + 1) % 4));
for (int k = 0; k < 4; k++) objects.Add(Line(L_VALLEY + k, N_FLOOR + 2 * k, N_OUTLET + k));
for (int k = 0; k < 4; k++) objects.Add(Line(L_POST + k, N_FLOOR + 2 * k, N_ROOF + k));
for (int k = 0; k < 4; k++) objects.Add(Line(L_ROOF + k, N_ROOF + k, N_ROOF + (k + 1) % 4));
// --- members -------------------------------------------------------------
int no = 1;
for (int k = 0; k < 4; k++) // legs, lower lift
objects.Add(new Rfem.StructureCore.Member {
No = no++, Line = L_LEG_LOWER + k, CrossSectionStart = 1,
MemberHingeStart = 1, MemberHingeEnd = 2 });
for (int k = 0; k < 4; k++) // legs, upper lift
objects.Add(new Rfem.StructureCore.Member {
No = no++, Line = L_LEG_UPPER + k, CrossSectionStart = 1,
MemberHingeStart = 1, MemberHingeEnd = 2 });
for (int i = 0; i < 8; i++) // floor ring, ribs in the plate
objects.Add(new Rfem.StructureCore.Member {
No = no++, Line = L_FLOOR_RING + i, CrossSectionStart = 2,
Type = Rfem.StructureCore.Member.Types.Type.Rib });
for (int i = 0; i < 16; i++) // vertical bracing
objects.Add(new Rfem.StructureCore.Member {
No = no++, Line = L_BRACE_LOWER + i, CrossSectionStart = 3,
MemberHingeStart = 1, MemberHingeEnd = 2 });
for (int i = 0; i < 8; i++) // horizontal bracing ring
{
// The torsional hinge sits at the CORNER end of each segment, so it
// alternates between the start and the end as the ring is walked.
var ring = new Rfem.StructureCore.Member {
No = no++, Line = L_BRACE_RING + i, CrossSectionStart = 3 };
if (i % 2 == 0) ring.MemberHingeStart = 1; else ring.MemberHingeEnd = 2;
objects.Add(ring);
}
for (int k = 0; k < 4; k++) // bin corner posts
// The channel has to open towards the inside of the bin, so each post is
// turned a further quarter turn as the corners are walked.
objects.Add(new Rfem.StructureCore.Member {
No = no++, Line = L_POST + k, CrossSectionStart = 4,
RotationAngle = Math.PI / 2.0 - k * Math.PI / 2.0 });
// --- surfaces ------------------------------------------------------------
// Outlet plate, then the four hopper panels, the four bin walls, the bin
// floor and the roof. Each panel is closed by walking its own face.
var outlet = new Rfem.StructureCore.Surface { No = 1 };
for (int k = 0; k < 4; k++) outlet.BoundaryLines.Add(L_OUTLET + k);
objects.Add(outlet);
for (int f = 0; f < 4; f++)
{
var panel = new Rfem.StructureCore.Surface { No = 2 + f };
panel.BoundaryLines.AddRange(new[] {
L_VALLEY + f, L_OUTLET + f, L_VALLEY + (f + 1) % 4,
L_FLOOR_RING + 2 * f + 1, L_FLOOR_RING + 2 * f });
objects.Add(panel);
}
for (int f = 0; f < 4; f++)
{
var wall = new Rfem.StructureCore.Surface { No = 6 + f };
wall.BoundaryLines.AddRange(new[] {
L_FLOOR_RING + 2 * f, L_FLOOR_RING + 2 * f + 1,
L_POST + (f + 1) % 4, L_ROOF + f, L_POST + f });
objects.Add(wall);
}
// The bin floor closes the stored solid from above. It is the free surface
// of the bulk material, not a steel plate, so it carries no thickness.
var floor = new Rfem.StructureCore.Surface {
No = 10, Type = Rfem.StructureCore.Surface.Types.Type.WithoutThickness };
for (int i = 0; i < 8; i++) floor.BoundaryLines.Add(L_FLOOR_RING + i);
objects.Add(floor);
var roof = new Rfem.StructureCore.Surface { No = 11 };
for (int k = 0; k < 4; k++) roof.BoundaryLines.Add(L_ROOF + k);
objects.Add(roof);
// Assigned AFTER the surfaces exist. A Thickness created first is accepted
// but its AssignedToSurfaces is dropped, and every surface then silently
// falls back to thickness 1 - which is how this model used to run with the
// hopper's 10 mm plate everywhere instead of 10 / 8 / 5 mm.
objects.Add(new Rfem.StructureCore.Thickness {
No = 1, Material = 1, UniformThickness = THICKNESS_HOPPER,
AssignedToSurfaces = { 2, 3, 4, 5 } });
objects.Add(new Rfem.StructureCore.Thickness {
No = 2, Material = 1, UniformThickness = THICKNESS_SHELL,
AssignedToSurfaces = { 1, 6, 7, 8, 9 } });
objects.Add(new Rfem.StructureCore.Thickness {
No = 3, Material = 1, UniformThickness = THICKNESS_ROOF,
AssignedToSurfaces = { 11 } });
// The stored solid, bounded by the hopper, the outlet plate and the bin
// floor. Its self weight is what loads the hopper panels.
objects.Add(new Rfem.StructureCore.Solid {
No = 1, Type = Rfem.StructureCore.Solid.Types.Type.Standard, Material = 2,
BoundarySurfaces = { 1, 2, 3, 4, 5, 10 } });
// Every leg base on the same elastic foundation.
var support = new Rfem.TypesForNodes.NodalSupport {
No = 1,
SpringX = SUPPORT_SPRING_HORIZONTAL,
SpringY = SUPPORT_SPRING_HORIZONTAL,
SpringZ = SUPPORT_SPRING_VERTICAL };
for (int k = 0; k < 4; k++) support.Nodes.Add(N_LEG_BASE + k);
objects.Add(support);
return objects;
}
// Analysis settings and load cases.
static List<IMessage> DefineLoading()
{
return new List<IMessage>
{
new Rfem.Loading.StaticAnalysisSettings { No = 1 },
new Rfem.Loading.StaticAnalysisSettings {
No = 2,
AnalysisType = Rfem.Loading.StaticAnalysisSettings.Types.AnalysisType.SecondOrderPDelta,
NumberOfLoadIncrements = 2
},
new Rfem.Loading.LoadCase {
No = 1, Name = "Self weight", StaticAnalysisSettings = 1,
SelfWeightActive = true
},
new Rfem.Loading.LoadCase {
No = 2, Name = "Stability - second order", StaticAnalysisSettings = 2,
ActionCategory = Rfem.Loading.LoadCase.Types.ActionCategory.PermanentImposedGq,
SelfWeightActive = true
},
};
}
ApplicationRfem? rfemApp = null;
try
{
rfemApp = new ApplicationRfem();
rfemApp.close_all_models(saveChanges: false);
rfemApp.create_model(MODEL_NAME);
rfemApp.delete_all_objects();
rfemApp.create_object_list(DefineStructure().Concat(DefineLoading()).ToList());
rfemApp.calculate_all(skipWarnings: true);
// Results: bending in the four hopper panels, which carry the stored solid.
var hopperPanels = new List<Rfem.Results.ResultsFilter> {
new Rfem.Results.ResultsFilter { ColumnId = "surface_no", FilterExpression = "2, 3, 4, 5" }
};
Console.WriteLine("\nHopper panels | Basic internal forces at grid points:");
Console.WriteLine(rfemApp.get_results(
resultsType: Rfem.Results.ResultsType.StaticAnalysisSurfacesBasicInternalForcesGridPoints,
filters: hopperPanels).Data);
Console.WriteLine("\nHopper panels | Basic internal forces at mesh nodes:");
Console.WriteLine(rfemApp.get_results(
resultsType: Rfem.Results.ResultsType.StaticAnalysisSurfacesBasicInternalForcesMeshNodes,
filters: hopperPanels).Data);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
if (rfemApp != null) rfemApp.close_connection();
}