diff --git a/src/Mod/Fem/femmesh/meshtools.py b/src/Mod/Fem/femmesh/meshtools.py index f3f1ede344..f57dd1f095 100644 --- a/src/Mod/Fem/femmesh/meshtools.py +++ b/src/Mod/Fem/femmesh/meshtools.py @@ -362,6 +362,69 @@ def get_femelement_sets(femmesh, femelement_table, fem_objects, femnodes_ele_tab FreeCAD.Console.PrintError('Error in get_femelement_sets -- > femelements_count_ok() failed!\n') +def get_femelement_direction1D_set(femmesh, femelement_table, beamrotation_objects, theshape=None): + ''' + get for each geometry edge direction, the normal and the element ids and write all into the beamrotation_objects + means no return value, we gone write into the beamrotation_objects dictionary + FEMRotations1D is a list of dictionaries for every beamdirection of all edges + beamrot_obj['FEMRotations1D'] = [ {'ids' : [theids], + 'direction' : direction, + 'normal' : normal}, + ... + ] + ''' + if len(beamrotation_objects) == 0: + # no beamrotation document object, all beams use standard rotation of 0 degree (angle), we need theshape (the shpae which was meshed) + # since ccx needs to split them in sets anyway we need to dake care of this too + rotations_ids = get_femelement_directions_theshape(femmesh, femelement_table, theshape) + # add normals for each direction + rotation_angle = 0 + for rot in rotations_ids: + rot['normal'] = get_beam_normal(rot['direction'], rotation_angle) + beamrotation_objects.append({'FEMRotations1D': rotations_ids, 'ShortName': 'Rstd'}) # key 'Object' will be empty + elif len(beamrotation_objects) == 1: + # one beamrotaion document object with no references, all beams use rotation from this object, we need theshape (the shpae which was meshed) + # since ccx needs to split them in sets anyway we need to dake care of this too + rotations_ids = get_femelement_directions_theshape(femmesh, femelement_table, theshape) + # add normals for each direction + rotation_angle = beamrotation_objects[0]['Object'].Rotation + for rot in rotations_ids: + rot['normal'] = get_beam_normal(rot['direction'], rotation_angle) + beamrotation_objects[0]['FEMRotations1D'] = rotations_ids + beamrotation_objects[0]['ShortName'] = 'R0' + elif len(beamrotation_objects) > 1: + # multiple beam rotation document objects, rotations defined by reference shapes, TODO implement this + # do not forget all the corner cases: + # one beam rotation object, but not all edges are ref shapes + # more than one beam rotation object, but not all edges are in the ref shapes + # for the both cases above, all other edges get standard rotation. + # more than one beam roataion objects and on has no ref shapes, all edges no in an rotation object use this rotation + # one edge is in more than one beam rotation object, error + # pre check, only one beam rotation with empty ref shapes is allowed + # we need theshape for multiple rotations too, because of the corner cases mentioned above + FreeCAD.Console.PrintError('Multiple Rotations not yet supported!\n') + for rot_object in beamrotation_objects: # debug print + print(rot_object['FEMRotations1D']) + + +def get_femelement_directions_theshape(femmesh, femelement_table, theshape): + # see get_femelement_direction1D_set + rotations_ids = [] + # add directions and all ids for each direction + for e in theshape.Shape.Edges: + the_edge = {} + the_edge['direction'] = e.Vertexes[1].Point - e.Vertexes[0].Point + edge_femnodes = femmesh.getNodesByEdge(e) # femnodes for the current edge + the_edge['ids'] = get_femelements_by_femnodes_std(femelement_table, edge_femnodes) # femelements for this edge + for rot in rotations_ids: + if rot['direction'] == the_edge['direction']: # tolerance will be managed by FreeCAD see https://forum.freecadweb.org/viewtopic.php?f=22&t=14179 + rot['ids'] += the_edge['ids'] + break + else: + rotations_ids.append(the_edge) + return rotations_ids + + def get_beam_normal(beam_direction, defined_angle): import math vector_a = beam_direction @@ -454,6 +517,8 @@ def get_elset_short_name(obj, i): return 'M' + str(i) elif hasattr(obj, "Proxy") and obj.Proxy.Type == 'Fem::FemElementGeometry1D': return 'B' + str(i) + elif hasattr(obj, "Proxy") and obj.Proxy.Type == 'Fem::FemElementRotation1D': + return 'R' + str(i) elif hasattr(obj, "Proxy") and obj.Proxy.Type == 'Fem::FemElementFluid1D': return 'F' + str(i) elif hasattr(obj, "Proxy") and obj.Proxy.Type == 'Fem::FemElementGeometry2D': diff --git a/src/Mod/Fem/femsolver/calculix/writer.py b/src/Mod/Fem/femsolver/calculix/writer.py old mode 100644 new mode 100755 index 6fd985ea08..d769c1a3a8 --- a/src/Mod/Fem/femsolver/calculix/writer.py +++ b/src/Mod/Fem/femsolver/calculix/writer.py @@ -337,14 +337,18 @@ class FemInputWriterCcx(FemInputWriter.FemInputWriter): f.write('** Element sets for materials and FEM element type (solid, shell, beam, fluid)\n') f.write('** written by {} function\n'.format(sys._getframe().f_code.co_name)) + # in any case if we have beams, we gone need the element ids for the rotation elsets + if self.beamsection_objects: + # we will need to split the beam even for one beamobj + # because no beam in z-direction can be used in ccx without a special adjustment + # thus they need an own ccx_elset + self.get_element_rotation1D_elements() + # get the element ids for face and edge elements and write them into the objects if len(self.shellthickness_objects) > 1: self.get_element_geometry2D_elements() elif len(self.beamsection_objects) > 1: self.get_element_geometry1D_elements() - # we will need to split the beams even for one beamobj - # because no beam in z-direction can be used in ccx without a special adjustment - # thus they need an own ccx_elset --> but this is ccx specific and thus should not be in input writer! elif len(self.fluidsection_objects) > 1: self.get_element_fluid1D_elements() @@ -631,25 +635,30 @@ class FemInputWriterCcx(FemInputWriter.FemInputWriter): beamsec_obj = ccx_elset['beamsection_obj'] elsetdef = 'ELSET=' + ccx_elset['ccx_elset_name'] + ', ' material = 'MATERIAL=' + ccx_elset['mat_obj_name'] + normal = ccx_elset['beam_normal'] if beamsec_obj.SectionType == 'Rectangular': height = beamsec_obj.RectHeight.getValueAs('mm') width = beamsec_obj.RectWidth.getValueAs('mm') section_type = ', SECTION=RECT' setion_geo = str(height) + ', ' + str(width) + '\n' setion_def = '*BEAM SECTION, ' + elsetdef + material + section_type + '\n' + setion_nor = str(normal[0]) + ', ' + str(normal[1]) + ', ' + str(normal[2]) + '\n' elif beamsec_obj.SectionType == 'Circular': radius = 0.5 * beamsec_obj.CircDiameter.getValueAs('mm') section_type = ', SECTION=CIRC' setion_geo = str(radius) + '\n' setion_def = '*BEAM SECTION, ' + elsetdef + material + section_type + '\n' + setion_nor = str(normal[0]) + ', ' + str(normal[1]) + ', ' + str(normal[2]) + '\n' elif beamsec_obj.SectionType == 'Pipe': radius = 0.5 * beamsec_obj.PipeDiameter.getValueAs('mm') thickness = beamsec_obj.PipeThickness.getValueAs('mm') section_type = ', SECTION=PIPE' setion_geo = str(radius) + ', ' + str(thickness) + '\n' setion_def = '*BEAM GENERAL SECTION, ' + elsetdef + material + section_type + '\n' + setion_nor = str(normal[0]) + ', ' + str(normal[1]) + ', ' + str(normal[2]) + '\n' f.write(setion_def) f.write(setion_geo) + f.write(setion_nor) elif 'fluidsection_obj'in ccx_elset: # fluid mesh fluidsec_obj = ccx_elset['fluidsection_obj'] elsetdef = 'ELSET=' + ccx_elset['ccx_elset_name'] + ', ' @@ -1071,72 +1080,94 @@ class FemInputWriterCcx(FemInputWriter.FemInputWriter): # 'ccx_elset_name' : 'ccx_identifier_elset' # 'mat_obj_name' : 'mat_obj.Name' # 'ccx_mat_name' : 'mat_obj.Material['Name']' !!! not unique !!! - # 'beamsection_obj' : 'beamsection_obj' if exists - # 'fluidsection_obj' : 'fluidsection_obj' if exists - # 'shellthickness_obj' : shellthickness_obj' if exists + # 'beamsection_obj' : 'beamsection_obj' if exists + # 'fluidsection_obj' : 'fluidsection_obj' if exists + # 'shellthickness_obj' : shellthickness_obj' if exists + # 'beam_normal' : normal vector for beams only # }, # {}, ... , {} ] # beam + # TODO support multiple beamrotations + # we do not need any more any data from the rotation document object, thus we do not need to save the rotation document object name in the else def get_ccx_elsets_single_mat_single_beam(self): mat_obj = self.material_objects[0]['Object'] beamsec_obj = self.beamsection_objects[0]['Object'] - elset_data = self.ccx_eedges - names = [{'short': 'M0'}, {'short': 'B0'}] - ccx_elset = {} - ccx_elset['ccx_elset'] = elset_data - ccx_elset['ccx_elset_name'] = get_ccx_elset_name_short(names) - ccx_elset['mat_obj_name'] = mat_obj.Name - ccx_elset['ccx_mat_name'] = mat_obj.Material['Name'] - ccx_elset['beamsection_obj'] = beamsec_obj - self.ccx_elsets.append(ccx_elset) + beamrot_data = self.beamrotation_objects[0] + for i, beamdirection in enumerate(beamrot_data['FEMRotations1D']): + elset_data = beamdirection['ids'] # ID's for this direction + names = [{'short': 'M0'}, {'short': 'B0'}, {'short': beamrot_data['ShortName']}, {'short': 'D' + str(i)}] + ccx_elset = {} + ccx_elset['ccx_elset'] = elset_data + ccx_elset['ccx_elset_name'] = get_ccx_elset_name_short(names) + ccx_elset['mat_obj_name'] = mat_obj.Name + ccx_elset['ccx_mat_name'] = mat_obj.Material['Name'] + ccx_elset['beamsection_obj'] = beamsec_obj + ccx_elset['beam_normal'] = beamdirection['normal'] # normal for this direction + self.ccx_elsets.append(ccx_elset) def get_ccx_elsets_single_mat_multiple_beam(self): mat_obj = self.material_objects[0]['Object'] + beamrot_data = self.beamrotation_objects[0] for beamsec_data in self.beamsection_objects: beamsec_obj = beamsec_data['Object'] - elset_data = beamsec_data['FEMElements'] - names = [{'short': 'M0'}, {'short': beamsec_data['ShortName']}] - ccx_elset = {} - ccx_elset['ccx_elset'] = elset_data - ccx_elset['ccx_elset_name'] = get_ccx_elset_name_short(names) - ccx_elset['mat_obj_name'] = mat_obj.Name - ccx_elset['ccx_mat_name'] = mat_obj.Material['Name'] - ccx_elset['beamsection_obj'] = beamsec_obj - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_multiple_mat_single_beam(self): - beamsec_obj = self.beamsection_objects[0]['Object'] - for mat_data in self.material_objects: - mat_obj = mat_data['Object'] - elset_data = mat_data['FEMElements'] - names = [{'short': mat_data['ShortName']}, {'short': 'B0'}] - ccx_elset = {} - ccx_elset['ccx_elset'] = elset_data - ccx_elset['ccx_elset_name'] = get_ccx_elset_name_short(names) - ccx_elset['mat_obj_name'] = mat_obj.Name - ccx_elset['ccx_mat_name'] = mat_obj.Material['Name'] - ccx_elset['beamsection_obj'] = beamsec_obj - self.ccx_elsets.append(ccx_elset) - - def get_ccx_elsets_multiple_mat_multiple_beam(self): - for beamsec_data in self.beamsection_objects: - beamsec_obj = beamsec_data['Object'] - for mat_data in self.material_objects: - mat_obj = mat_data['Object'] - beamsec_ids = set(beamsec_data['FEMElements']) - mat_ids = set(mat_data['FEMElements']) - elset_data = list(sorted(beamsec_ids.intersection(mat_ids))) # empty intersection sets possible + beamsec_ids = set(beamsec_data['FEMElements']) + for i, beamdirection in enumerate(beamrot_data['FEMRotations1D']): + beamdir_ids = set(beamdirection['ids']) + elset_data = list(sorted(beamsec_ids.intersection(beamdir_ids))) # empty intersection sets possible if elset_data: - names = [{'short': mat_data['ShortName']}, {'short': beamsec_data['ShortName']}] + names = [{'short': 'M0'}, {'short': beamsec_data['ShortName']}, {'short': beamrot_data['ShortName']}, {'short': 'D' + str(i)}] ccx_elset = {} ccx_elset['ccx_elset'] = elset_data ccx_elset['ccx_elset_name'] = get_ccx_elset_name_short(names) ccx_elset['mat_obj_name'] = mat_obj.Name ccx_elset['ccx_mat_name'] = mat_obj.Material['Name'] ccx_elset['beamsection_obj'] = beamsec_obj + ccx_elset['beam_normal'] = beamdirection['normal'] # normal for this direction self.ccx_elsets.append(ccx_elset) + def get_ccx_elsets_multiple_mat_single_beam(self): + beamsec_obj = self.beamsection_objects[0]['Object'] + beamrot_data = self.beamrotation_objects[0] + for mat_data in self.material_objects: + mat_obj = mat_data['Object'] + mat_ids = set(mat_data['FEMElements']) + for i, beamdirection in enumerate(beamrot_data['FEMRotations1D']): + beamdir_ids = set(beamdirection['ids']) + elset_data = list(sorted(mat_ids.intersection(beamdir_ids))) + if elset_data: + names = [{'short': mat_data['ShortName']}, {'short': 'B0'}, {'short': beamrot_data['ShortName']}, {'short': 'D' + str(i)}] + ccx_elset = {} + ccx_elset['ccx_elset'] = elset_data + ccx_elset['ccx_elset_name'] = get_ccx_elset_name_short(names) + ccx_elset['mat_obj_name'] = mat_obj.Name + ccx_elset['ccx_mat_name'] = mat_obj.Material['Name'] + ccx_elset['beamsection_obj'] = beamsec_obj + ccx_elset['beam_normal'] = beamdirection['normal'] # normal for this direction + self.ccx_elsets.append(ccx_elset) + + def get_ccx_elsets_multiple_mat_multiple_beam(self): + beamrot_data = self.beamrotation_objects[0] + for beamsec_data in self.beamsection_objects: + beamsec_obj = beamsec_data['Object'] + beamsec_ids = set(beamsec_data['FEMElements']) + for mat_data in self.material_objects: + mat_obj = mat_data['Object'] + mat_ids = set(mat_data['FEMElements']) + for i, beamdirection in enumerate(beamrot_data['FEMRotations1D']): + beamdir_ids = set(beamdirection['ids']) + elset_data = list(sorted(beamsec_ids.intersection(mat_ids).intersection(beamdir_ids))) # empty intersection sets possible + if elset_data: + names = [{'short': mat_data['ShortName']}, {'short': beamsec_data['ShortName']}, {'short': beamrot_data['ShortName']}, {'short': 'D' + str(i)}] + ccx_elset = {} + ccx_elset['ccx_elset'] = elset_data + ccx_elset['ccx_elset_name'] = get_ccx_elset_name_short(names) + ccx_elset['mat_obj_name'] = mat_obj.Name + ccx_elset['ccx_mat_name'] = mat_obj.Material['Name'] + ccx_elset['beamsection_obj'] = beamsec_obj + ccx_elset['beam_normal'] = beamdirection['normal'] # normal for this direction + self.ccx_elsets.append(ccx_elset) + # fluid def get_ccx_elsets_single_mat_single_fluid(self): mat_obj = self.material_objects[0]['Object'] @@ -1283,7 +1314,7 @@ class FemInputWriterCcx(FemInputWriter.FemInputWriter): # Helpers -# ccx elset names: M .. Material, B .. Beam, F .. Fluid, S .. Shell, TODO write comment into input file to elset ids and elset attributes +# ccx elset names: M .. Material, B .. Beam, R .. BeamRotation, D ..Direction, F .. Fluid, S .. Shell, TODO write comment into input file to elset ids and elset attributes def get_ccx_elset_name_standard(names): # standard max length = 80 ccx_elset_name = '' diff --git a/src/Mod/Fem/femsolver/writerbase.py b/src/Mod/Fem/femsolver/writerbase.py index b29856c945..1e691a4246 100644 --- a/src/Mod/Fem/femsolver/writerbase.py +++ b/src/Mod/Fem/femsolver/writerbase.py @@ -78,6 +78,10 @@ class FemInputWriter(): self.ccx_efaces = 'Efaces' self.ccx_eedges = 'Eedges' self.ccx_elsets = [] + if hasattr(self.mesh_object, "Shape"): + self.theshape = self.mesh_object.Shape + elif hasattr(self.mesh_object, "Part"): + self.theshape = self.mesh_object.Part self.femmesh = self.mesh_object.FemMesh self.femnodes_mesh = {} self.femelement_table = {} @@ -195,6 +199,12 @@ class FemInputWriter(): self.femelement_table = FemMeshTools.get_femelement_table(self.femmesh) FemMeshTools.get_femelement_sets(self.femmesh, self.femelement_table, self.beamsection_objects) + def get_element_rotation1D_elements(self): + # get for each geometry edge direction the element ids and rotation norma + if not self.femelement_table: + self.femelement_table = FemMeshTools.get_femelement_table(self.femmesh) + FemMeshTools.get_femelement_direction1D_set(self.femmesh, self.femelement_table, self.beamrotation_objects, self.theshape) + def get_element_fluid1D_elements(self): # get element ids and write them into the objects print("Fluid sections")