今天承接[Day25]的內容,說明程式碼的內容。
首先,我們需要一些helper function,這些都是熟面孔了,不用多作介紹了吧。
#create_solid_segs.py
from datetime import datetime
from enum import Enum
from uuid import uuid4
import ansa
from ansa import base, constants
class NotSupportedElemTypeError(Exception):
pass
class LSDYNAType(str, Enum):
ALL: str = '__ALL_ENTITIES__'
PART: str = 'ANSAPART'
NODE: str = 'NODE'
ELEMENT: str = '__ELEMENTS__'
SOLID: str = 'ELEMENT_SOLID'
SHELL: str = 'ELEMENT_SHELL'
PROPERTY: str = '__PROPERTIES__'
MATERIAL: str = '__MATERIALS__'
SET: str = 'SET'
CONTACT: str = 'CONTACT'
SEGMENT: str = 'SEGMENT'
SOLIDFACET: str = 'SOLIDFACET'
class SolidType(str, Enum):
TETRA: str = 'TETRA'
HEXA: str = 'HEXA'
def _get_u_name():
return datetime.now().strftime('%Y%m%d_%H%M%S') + '_' + str(uuid4().hex)[:6]
def _create_entity(type_, fields, deck=None):
deck = deck or constants.LSDYNA
return base.CreateEntity(deck, type_, fields)
def create_set(entities=None, name=None, deck=None):
deck = deck or constants.LSDYNA
type_ = LSDYNAType.SET
name = name or _get_u_name()
fields = {'Name': name}
set_entity = _create_entity(type_, fields, deck=deck)
if entities is not None:
base.AddToSet(set_entity, entities)
return set_entity
def create_segment(vals=None, deck=None):
deck = deck or constants.LSDYNA
type_ = LSDYNAType.SEGMENT
vals = vals or {}
fields = vals
return _create_entity(type_, fields, deck=deck)
base.PickEntities可以讓我們藉由GUI介面來選取Entity,一般需要三個參數,deck、types及initial_type。
其中types為可以選取的Entity種類,而initial_type為一開始預設的Entity種類。
這裡我們使用base.PickEntities來選取想要的SolidFacet Entity,並命名一個變數picked_sfs來接收回傳值。如果有選到的話會回傳一個含有選擇SolidFact Entity的list,如果沒有選到的話會回傳一個空的list。
我們用一個if來判斷picked_sfs是否為空,如果不為空的話,則執行後續建立segment的程式。
#create_solid_segs.py
if __name__ == '__main__':
deck = constants.LSDYNA
picked_sfs = base.PickEntities(
deck, LSDYNAType.SOLIDFACET, initial_type=LSDYNAType.SOLIDFACET)
if picked_sfs:
main(picked_sfs, deck=deck)
參考[Day25]的說明,我們可以建立:
_type_mappings_tetra來對應TETRA四個面的node順序。
_type_mappings_hexa來對應HEAX六個面的node順序。
_index_mappings來對應TRI3、QUAD4、TRI6及QUAD8等不同shell,所需產生segment(s)的node順序。

此外,我們也在這裡建立了四組下面會用到的keys。選擇於此處建立的原因是因為,這些keys在迴圈都是不變的。如果置於迴圈中,只是重複建立這些keys。
#create_solid_segs.py
def main(sfs, deck=None):
deck = deck or constants.LSDYNA
_type_mappings_tetra = dict(
zip(range(3715, 3719), [(1, 2, 3),
(1, 2, 4),
(2, 3, 4),
(1, 3, 4)]))
_type_mappings_hexa = dict(
zip(range(3715, 3721), [(1, 2, 3, 4),
(1, 2, 5, 6),
(2, 3, 6, 7),
(3, 4, 7, 8),
(1, 4, 5, 8),
(5, 6, 7, 8)]))
_index_mappings = {
3: [(1, 2, 3)],
4: [(1, 2, 3, 4)],
6: [(1, 4, 6), (2, 5, 4), (3, 6, 5), (4, 5, 6)],
8: [(1, 5, 8), (2, 6, 5), (3, 7, 6), (4, 8, 7), (5, 6, 7, 8)]}
shell_tri3_node_keys = ('N1', 'N2', 'N3')
shell_quad4_node_keys = ('N1', 'N2', 'N3', 'N4')
solid_node_keys = ('N1', 'N2', 'N3', 'N4', 'N5', 'N6', 'N7', 'N8')
segment_keys = ('Node 1', 'Node 2', 'Node 3', 'Node 4')
list,命名為selected_shells及to_dels,分別來收集符合條件及不符合條件的shell。solidfacet打一個迴圈,針對每一個solidfacet Entity:
solidfacet.get_entity_values 取得其EID。EID取得其Solid Entity。solid.get_entity_values 取得solid_node_keys的card_values,並將其轉換為list存入solid_node_ents,方便後面做indexing操作時使用。solid.get_entity_values 取得type,存入solid_type。Entity._type取得_type存入s_type。solid_type,判斷此Entity為TETRA或HEXA,分別取得其facet_seq(該SolidFacet面的node順序)及shell_node_keys。如果既不是TETRA也不是HEXA,則raiseNotSupportedElemTypeError。set comprehensions收集所有的node Entity並存入f_node_ents。注意此處的index-1是因為Python的index由0開始,而我們在_type_mappings_tetra或_type_mappings_hexa的node順序是由1開始。base.CreateShellsFromSolidFacets搭配skin可以產生該SolidFacet Entity的所有shell Entity。list,selected_shells及to_dels,分別來收集符合條件及不符合條件的shell。shell打一個迴圈,針對每一個shell Entity:
shell.get_entity_values 取得其shell_node_keys的card_values,並存入shell_node_ents。shell_node_ents皆在f_node_ents內,如果是則加入selected_shells,如果不是則加入to_dels。#create_solid_segs.py
selected_shells, to_dels = [], []
for s in sfs:
eid = s.get_entity_values(deck, ['EID'])['EID']
solid = base.GetEntity(deck, LSDYNAType.SOLID, eid)
solid_node_ents = list(solid.get_entity_values(
deck, solid_node_keys).values()) # for indexing
solid_type = solid.get_entity_values(deck, ['type'])['type']
s_type = s._type
if solid_type == SolidType.TETRA:
facet_seq = _type_mappings_tetra.get(s_type)
shell_node_keys = shell_tri3_node_keys
elif solid_type == SolidType.HEXA:
facet_seq = _type_mappings_hexa.get(s_type)
shell_node_keys = shell_quad4_node_keys
else:
raise NotSupportedElemTypeError
# become set, for later in operator
f_node_ents = {solid_node_ents[i-1] for i in facet_seq}
shells = base.CreateShellsFromSolidFacets(
'skin', ret_ents=True, solids=solid)
for shell in shells:
shell_node_ents = shell.get_entity_values(
deck, shell_node_keys).values()
conds = (shell_node_ent in f_node_ents
for shell_node_ent in shell_node_ents)
if all(conds):
selected_shells.append(shell)
else:
to_dels.append(shell)
set,命名為segments來收集建立的segment。selected_shells打一個迴圈,針對每一個shell Entity:
shell.get_entity_values 取得solid_node_keys的card_values,並將其轉換為list存入nodes,方便後面做indexing操作時使用(備註)。node數量搭配_index_mappings可以得到該shell Entity準備產生segment(s)的node順序,存入idxes。idxes is None,則raise NotSupportedElemTypeError。list comprehensions可以取得各segment所需的node Entity,存入seqs。seqs打一個迴圈,針對每一個seq:
segment Entity。segment Entity加入segments。set Entity,將segments加入該Entity。shell Entity。#create_solid_segs.py
segments = set()
for shell in selected_shells:
nodes = list(shell.get_entity_values(
deck, solid_node_keys).values()) # for indexing
n_nodes = len(nodes)
idxes = _index_mappings.get(n_nodes)
if idxes is None:
raise NotSupportedElemTypeError
seqs = [[nodes[_idx-1] for _idx in idxs]
for idxs in idxes]
for seq in seqs:
fields = dict(zip(segment_keys, seq))
segment = create_segment(fields, deck=deck)
segments.add(segment)
segments_set = create_set(segments, deck=deck)
base.DeleteEntity([*selected_shells, *to_dels])
聰明的您可能發現,這邊不是在做shell.get_entity_values,怎麼會取用solid_node_keys呢?原因是因為:
shell Entity ,其node數僅可能為3、4、6及8,故solid_node_keys是足以覆蓋這些可能性的。Entity.get_entity_values時,如果所想get的key是不存在的話,並不會報錯。所以可以說,我們取巧的使用了solid_node_keys的值。
這樣的code好嗎?我們不置可否。coding的時候,手指總是會展現當下最直接想到的解法。
我們是在已經寫完code,於code review時才發現,原來我們的第一個直覺是這樣子做呀!覺得滿有趣的,所以決定維持現有的code,不作更改。