У меня есть 10.000 ".step" файлов, содержащих консольные балки со случайными многоугольными сечениями. Я хотел бы автоматически проанализировать максимальное отклонение, изгибающую нагрузку и / или частотную характеристику (резонансную частоту) каждого луча и записать результаты в файл.
В каждом сценарии один конец балки должен быть фиксирован. К другому концу балки я бы хотел приложить нагрузку. Нагрузка и ограничения должны быть одинаковыми для всех балок.
Проблемы:
- найти подходящую библиотеку
- сетчатые тела для анализа
- назначить ограничения граням (не все балки имеют одинаковое количество граней)
Я пытался автоматизировать этот процесс в Fusion360, но, к сожалению, в их API отсутствует поддержка моделирования методом конечных элементов (FEA).
Я пытался автоматизировать процесс с помощью FreeCAD, но я не могу полностью автоматизировать анализ FEA, поскольку FreeCAD использует GMSH и CalculiX, и я не смог найти полезную документацию.
Я счастлив использовать несколько сценариев, если это необходимо, например, автоматизировать создание сетки с помощью GMSH, настроить симуляцию с помощью FreeCAD и решить с помощью Calculix. Если возможно, я бы хотел сделать все это на python.
Ниже приведен код для макроса FreeCAD, который генерирует случайный луч и экспортирует файл .step в папку, заданную в переменной cur_path.
import numpy as np
import Part
###
# Polygon Generator
# (Source: /8285101/algoritm-generatsii-sluchainogo-2d-mnogougolnika)
###
import math, random
def generatePolygon( ctrX, ctrY, aveRadius, irregularity, spikeyness, numVerts ) :
'''
Start with the centre of the polygon at ctrX, ctrY,
then creates the polygon by sampling points on a circle around the centre.
Randon noise is added by varying the angular spacing between sequential points,
and by varying the radial distance of each point from the centre.
Params:
ctrX, ctrY - coordinates of the "centre" of the polygon
aveRadius - in px, the average radius of this polygon, this roughly controls how large the polygon is, really only useful for order of magnitude.
irregularity - [0,1] indicating how much variance there is in the angular spacing of vertices. [0,1] will map to [0, 2pi/numberOfVerts]
spikeyness - [0,1] indicating how much variance there is in each vertex from the circle of radius aveRadius. [0,1] will map to [0, aveRadius]
numVerts - self-explanatory
Returns a list of vertices, in CCW order.
'''
irregularity = clip( irregularity, 0,1 ) * 2*math.pi / numVerts
spikeyness = clip( spikeyness, 0,1 ) * aveRadius
# generate n angle steps
angleSteps = []
lower = (2*math.pi / numVerts) - irregularity
upper = (2*math.pi / numVerts) + irregularity
sum = 0
for i in range(numVerts) :
tmp = random.uniform(lower, upper)
angleSteps.append( tmp )
sum = sum + tmp
# normalize the steps so that point 0 and point n+1 are the same
k = sum / (2*math.pi)
for i in range(numVerts) :
angleSteps[i] = angleSteps[i] / k
# now generate the points
points = []
angle = random.uniform(0, 2*math.pi)
for i in range(numVerts) :
r_i = clip( random.gauss(aveRadius, spikeyness), 0, 2*aveRadius )
x = ctrX + r_i*math.cos(angle)
y = ctrY + r_i*math.sin(angle)
points.append( (int(x),int(y)) )
angle = angle + angleSteps[i]
return points
def clip(x, min, max) :
if( min > max ) : return x
elif( x < min ) : return min
elif( x > max ) : return max
else :
cur_path = "/GeneratedBeamData"
max_verts = 20
min_verts = 3
max_radius = 30
min_radius = 10
extrude_length = 200.0 #[cm]
IMG_MAX_X = 128
IMG_MAX_Y = 128
# Open New Doc
App.newDocument("cantilever")
App.setActiveDocument("cantilever")
App.ActiveDocument=App.getDocument("cantilever")
# create new object
App.activeDocument().addObject('PartDesign::Body','Body')
# Start Sketch
App.activeDocument().Body.newObject('Sketcher::SketchObject','Sketch')
App.activeDocument().Sketch.Support = (App.activeDocument().XY_Plane, [''])
App.activeDocument().Sketch.MapMode = 'FlatFace'
App.ActiveDocument.recompute()
print("Started Sketch successfully")
#
# Draw Polygon
#
# randomly select Nr. of Vertices
avg_rad = random.randint(min_radius,max_radius)
nr_verts = random.randint(min_verts,max_verts)
verts = generatePolygon(IMG_MAX_X/2,IMG_MAX_Y/2, avg_rad, 2, 1, nr_verts)
while (len(verts) > len(set(verts))):
avg_rad = random.randint(min_radius,max_radius)
nr_verts = random.randint(min_verts,max_verts)
verts = generatePolygon(IMG_MAX_X/2,IMG_MAX_Y/2, avg_rad, 2, 1, nr_verts)
## center vertices
cverts = []
for pt in verts:
cverts.append((pt[0]-IMG_MAX_X, pt[1]-IMG_MAX_Y))
verts = cverts
print(verts)
# start a line
start_pt = verts[0]
cur_pt = verts[0]
next_pt = verts[1]
# draw initial line segment
App.ActiveDocument.Sketch.addGeometry(
Part.LineSegment(
App.Vector(cur_pt[0],cur_pt[1],0),
App.Vector(next_pt[0],next_pt[1],0)),False)
cur_pt = next_pt
line_nr = 0 # drew 0th line
print("Drew First line successfully")
for i in range(2, len(verts)):
next_pt = verts[i]
App.ActiveDocument.Sketch.addGeometry(
Part.LineSegment(
App.Vector(cur_pt[0],cur_pt[1],0),
App.Vector(next_pt[0],next_pt[1],0)),False)
line_nr += 1 # increment line counter
# define constraint
# Sketch.addConstraint(
#Sketcher.Constraint('Coincident',LineFixed,PointOfLineFixed,LineMoving,PointOfLineMoving))
App.ActiveDocument.Sketch.addConstraint(
Sketcher.Constraint("Coincident",line_nr-1,2,line_nr,1))
cur_pt = next_pt
print("Drew %d line successfully" % line_nr)
# Close the loop
print(cur_pt)
print(start_pt)
App.ActiveDocument.Sketch.addGeometry(
Part.LineSegment(
App.Vector(cur_pt[0],cur_pt[1],0),
App.Vector(start_pt[0],start_pt[1],0)),False)
line_nr += 1
#App.ActiveDocument.Sketch.addConstraint(
# Sketcher.Constraint('Coincident',line_nr-1,2,0,1))
print("Closed Shape")
# Extrude profile
# Get the profile defined by the polygon
App.getDocument("cantilever").recompute()
App.activeDocument().Body.newObject("PartDesign::Pad","Pad")
App.activeDocument().Pad.Profile = App.activeDocument().Sketch
# Extrusion
App.activeDocument().Pad.Length = extrude_length
App.ActiveDocument.recompute()
App.ActiveDocument.Pad.Length = extrude_length
App.ActiveDocument.Pad.Length2 = 100.00
App.ActiveDocument.Pad.Type = 0
App.ActiveDocument.Pad.UpToFace = None
App.ActiveDocument.Pad.Reversed = 0
App.ActiveDocument.Pad.Midplane = 0
App.ActiveDocument.Pad.Offset = 0.000000
App.ActiveDocument.recompute()
print("Extruded Polygon")
# Save STEP
__objs__=[]
__objs__.append(FreeCAD.getDocument("cantilever").getObject("Body"))
import ImportGui
ImportGui.export(__objs__,(u""+cur_path+"/body.step"))
Ниже вы найдете GMSH-скрипт, который я использовал для генерации и экспорта сетки в виде MSH-файла, используя входной файл .step. (Я надеялся, что смогу импортировать этот файл .msh в FreeCAD для автоматизации симуляции FEA.)
Merge "test.step";
Mesh.Algorithm3D = 5;
// 1=Delaunay, 4=Frontal, 5=Frontal Delaunay, 6=Frontal Hex, 7=MMG3D, 9=R-tree
// default = 1
//Mesh 2; // surface
Mesh 3; // volume
Mesh.Format = 2;
// 1=msh, 2=unv, 10=automatic, 19=vrml, 27=stl, 30=mesh, 31=bdf, 32=cgns, 33=med, 40=ply2
// default = 10
Mesh.SaveAll = 1;
// Ignore Physical definitions and save all elements
Save "mymesh.msh";