今天我們開始來探討,如何能夠有邏輯地來產生node及element Entity,而不是每需要一個Entity就直接呼叫一次creator function。
今天先改進Plate,[Day14]繼續討論如何改進box。
目前的Plate是由單個QUAD4 element構成。
QUAD4 element呢?QUAD4 element呢?很多次如果...之後,是不是就可以做出一個網格大小合理的平板了呢?
有了初步的想法之後,我們來思考一下如何執行? 我們希望能夠寫出一個function,在給定x長度、y長度、x及y方向各自元素個數後,能回傳所有的座標(我們稱作grid)。
我們的解法是使用numpy的meshgrid function來產生(x, y)座標,z座標則由numpy的ones function產生,我們於其後乘以一個z_elv,用以調整平板的高度(即調整z座標)。
最後,我們還提供了兩個微調的功能,分別是調整旋轉角度及將原點對準某個(x, y)座標(即平移整個平板)。其中旋轉的功能比較難,我們參考了Stack Overflow
的作法,使用了scipy.spatial.transform內的Rotation.from_rotvec function 。
# gen_seqs.py
from scipy.spatial.transform import Rotation as scipy_rotation
def _gen_one_layer_grids(l, w, en1, en2, *, z_elv=None, move_xy=None, rot_angle=None):
m1, m2 = en1+1, en2+1
x = np.linspace(0, l, m1)
y = np.linspace(0, w, m2)
X, Y = np.meshgrid(x, y)
x_shape = X.shape
z_elv = z_elv or 0
Z = np.ones(x_shape)*z_elv
if rot_angle is not None:
XYZ = np.array([X.ravel(), Y.ravel(), Z.ravel()]).transpose()
r = scipy_rotation.from_rotvec(
rot_angle*np.array([0, 0, 1]), degrees=True)
XYZrot = r.apply(XYZ)
X = XYZrot[:, 0].reshape(x_shape)
Y = XYZrot[:, 1].reshape(x_shape)
Z = XYZrot[:, 2].reshape(x_shape)
if move_xy is not None:
ox, oy = move_xy
X += ox
Y += oy
yield from (X.flat, Y.flat, Z.flat)
接下來我們用gen_shell_grids來幫_gen_one_layer_grids做一個轉置,這樣就可以得到每一個grid的座標值了。
# gen_seqs.py
def gen_shell_grids(l, w, en1, en2, *, z_elv, move_xy, rot_angle):
return zip(*_gen_one_layer_grids(l, w, en1, en2,
z_elv=z_elv,
move_xy=move_xy,
rot_angle=rot_angle))
Plate Seqs這個部份就需要大家發揮一點想像力。
我們知道LS-DYNA的QUAD4 element是逆時針建立其四個node,所以我們想要達成的是:
x方向,不斷逆時針寫出每個QUAD4 element的四個node id,直到x方向寫到了指定個數。y座標往上走一層,繼續往x方向寫指定個數,直到y方向也寫到指定個數為止。# gen_seqs.py
def gen_shell_seqs(en1, en2, *, start=1):
en = en1*en2
cnt, total_cnt = 0, 0
while True:
if total_cnt == en:
break
if cnt == en1:
cnt = 0
else:
n1, n2, n3, n4 = start, start+1, start+en1+2, start+en1+1
total_cnt += 1
cnt += 1
yield (n1, n2, n3, n4)
start += 1
舉例來說,這邊我們想要建立一個x方向有2個element,y方向有2個element的plate。
en1, en2 = 2, 2
list(gen_shell_seqs(en1, en2))
其輸出為:
list(gen_shell_seqs(en1, en2))=[(1, 2, 5, 4), (2, 3, 6, 5), (4, 5, 8, 7), (5, 6, 9, 8)]

由於gen_shell_seqs是一個generator,所以需要使用list才能print出其內的元素。
有了gen_shell_grids與gen_shell_seqs之後,我們可以開始來實作create_plate_nodes function。
create_plate_nodes先透過gen_shell_grids取得shell_grids的generator。list comprehensions配合create_node及上述generator,產生了node Entities,並收集為一list。gen_shell_seqs,得到一個generator,作為下一步create_plate_q4shells的準備。node Entities及shell_seqs。# creators.py
def create_plate_nodes(l,
w,
en1,
en2,
node_start_id,
z_elv=None,
move_xy=None,
rot_angle=None,
deck=None):
deck = deck or constants.LSDYNA
shell_grids = gen_shell_grids(
l, w, en1, en2, z_elv=z_elv, move_xy=move_xy, rot_angle=rot_angle)
keys = ('NID', 'X', 'Y', 'Z')
nodes = [create_node(dict(zip(keys, (nid, *xyz))), deck=deck)
for nid, xyz in enumerate(shell_grids, start=node_start_id)]
shell_seqs = gen_shell_seqs(en1, en2, start=node_start_id)
return nodes, shell_seqs
create_plate_q4shells的寫法與create_plate_nodes差不多。我們透過create_plate_nodes的第二個回傳值shell_seqs配合create_shell來產生QUAD4 Entities,並收集為一list後回傳。
# creators.py
def create_plate_q4shells(shell_seqs, shell_start_id, pid, deck=None):
deck = deck or constants.LSDYNA
keys = ('type', 'PID', 'EID', 'N1', 'N2', 'N3', 'N4')
type_ = ShellType.QUAD
return [create_shell(dict(zip(keys, (type_, pid, eid, *ns))), deck=deck)
for eid, ns in enumerate(shell_seqs, start=shell_start_id)]
