FEM: Add automatic transfinite volume algorithm
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>401</width>
|
||||
<height>469</height>
|
||||
<width>402</width>
|
||||
<height>464</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>385</width>
|
||||
<height>304</height>
|
||||
<width>444</width>
|
||||
<height>504</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -160,7 +160,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>All volume surfaces must be transfinite. If the surfaces have different mesh types (quads vs. triangles) the volume must also be meshed with mixed elements.</string>
|
||||
<string>All volume surfaces must be transfinite. If the surfaces have different mesh types (quads vs. triangles) the volume must also be meshed with mixed elements enabled.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
@@ -199,8 +199,150 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="Automation">
|
||||
<property name="toolTip">
|
||||
<string>If activated all edges and surface belonging to the selected volumes, that do not have a transfinite definitions already, become transfinite automatically.
|
||||
Use strategically placed transfinite curve and surface definitions to guide the automatic algorithm.
|
||||
|
||||
Notes:
|
||||
1. User defined curves propagated to the opposite edges of faces, over all selected faces.
|
||||
2. Works with lany number of volumes, either connected or unconnected </string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Auto apply transfinite surfaces and curves</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Nodes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Distribution</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QComboBox" name="Distribution">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Constant</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bump</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bump Inverted</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Progression</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Progression Inverted</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::DoubleSpinBox" name="Coefficient">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::IntSpinBox" name="Nodes">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Surface</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QComboBox" name="Orientation">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="Recombine">
|
||||
<property name="text">
|
||||
<string>Recombine</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::IntSpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>Gui/SpinBox.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::DoubleSpinBox</class>
|
||||
<extends>QDoubleSpinBox</extends>
|
||||
<header>Gui/SpinBox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -450,7 +450,7 @@ class GmshTools(ObjectTools):
|
||||
search_ele_in_shape_to_mesh = False
|
||||
if not self.part_obj.Shape.isSame(sub[0].Shape):
|
||||
Console.PrintLog(
|
||||
" One element of the mesh refinement {} is "
|
||||
"One element of the mesh refinement {} is "
|
||||
"not an element of the Part to mesh.\n"
|
||||
"But we are going to try to find it in "
|
||||
"the Shape to mesh :-)\n".format(mr_obj.Name)
|
||||
@@ -482,8 +482,8 @@ class GmshTools(ObjectTools):
|
||||
duplicates = duplicates_set.intersection(elements)
|
||||
if duplicates:
|
||||
Console.PrintError(
|
||||
"The elements {} of the mesh distance {} have been added "
|
||||
"to another mesh distance already.\n".format(
|
||||
"The elements {} of the mesh refinement {} have been added"
|
||||
"to another mesh refinement already.\n".format(
|
||||
duplicates, mr_obj.Name
|
||||
)
|
||||
)
|
||||
@@ -971,6 +971,7 @@ class GmshTools(ObjectTools):
|
||||
def get_transfinite_data(self):
|
||||
|
||||
# transfinite curves
|
||||
edge_map = {}
|
||||
transfinite_curve_list = self._get_definitions_of_type("Fem::MeshTransfiniteCurve")
|
||||
if not transfinite_curve_list:
|
||||
pass
|
||||
@@ -987,37 +988,13 @@ class GmshTools(ObjectTools):
|
||||
):
|
||||
self.outputCompoundWarning()
|
||||
|
||||
for mr_obj in transfinite_curve_list:
|
||||
if mr_obj.Suppressed:
|
||||
continue
|
||||
|
||||
if mr_obj.References:
|
||||
|
||||
# collect all elements!
|
||||
elements = self._get_reference_elements(mr_obj, self.transfinite_curve_elements)
|
||||
if not elements:
|
||||
Console.PrintError( ("The transfinite curve {} is not used because no unique"
|
||||
"elements are selected.\n").format(mr_obj.Name))
|
||||
continue
|
||||
|
||||
idx_dict = self._element_list_to_shape_idx_dict(elements)
|
||||
if idx_dict["Edge"]:
|
||||
|
||||
definition = tft.TFCurveDefinition.from_tfcurve_obj(mr_obj)
|
||||
prefix = definition.tag_prefix()
|
||||
settings = definition.to_gmshtools_setting()
|
||||
settings["tag"] = ",".join(prefix+str(i) for i in idx_dict["Edge"])
|
||||
|
||||
self.transfinite_curve_settings.append(settings)
|
||||
|
||||
else:
|
||||
Console.PrintError(
|
||||
"The transfinite curve {} is not used to create the mesh "
|
||||
"because the reference list is empty.\n".format(mr_obj.Name)
|
||||
)
|
||||
|
||||
print("setup transfinite edge map")
|
||||
edge_map = tft.setup_transfinite_edge_map(self.part_obj.Shape, transfinite_curve_list)
|
||||
# the settings are only created at the very end, when we know if surface and volume atomation added
|
||||
# addiotional transfinite curves
|
||||
|
||||
# transfinite surfaces
|
||||
surface_map = {}
|
||||
transfinite_surface_list = self._get_definitions_of_type("Fem::MeshTransfiniteSurface")
|
||||
if not transfinite_surface_list:
|
||||
pass
|
||||
@@ -1034,19 +1011,31 @@ class GmshTools(ObjectTools):
|
||||
):
|
||||
self.outputCompoundWarning()
|
||||
|
||||
surface_map = tft.setup_transfinite_surface_map(self.part_obj.Shape, transfinite_surface_list)
|
||||
|
||||
# handle surface automation
|
||||
for mr_obj in transfinite_surface_list:
|
||||
|
||||
if mr_obj.Suppressed:
|
||||
continue
|
||||
|
||||
if mr_obj.References:
|
||||
|
||||
# collect all elements!
|
||||
elements = self._get_reference_elements(mr_obj, self.transfinite_surface_elements)
|
||||
elements = self._get_reference_elements(
|
||||
mr_obj, self.transfinite_surface_elements
|
||||
)
|
||||
if not elements:
|
||||
Console.PrintError( ("The transfinite surface {} is not used because no unique"
|
||||
"elements are selected.\n").format(mr_obj.Name))
|
||||
Console.PrintError(
|
||||
(
|
||||
"The transfinite surface {} is not used because no unique"
|
||||
"elements are selected.\n"
|
||||
).format(mr_obj.Name)
|
||||
)
|
||||
continue
|
||||
|
||||
# We handle surface settings here, and not via surface map only, as it could be a n-sided face with vertices,
|
||||
# and that is not handled by the transfinitetools yet
|
||||
idx_dict = self._element_list_to_shape_idx_dict(elements)
|
||||
if idx_dict["Face"]:
|
||||
settings = {}
|
||||
@@ -1060,21 +1049,16 @@ class GmshTools(ObjectTools):
|
||||
|
||||
self.transfinite_surface_settings.append(settings)
|
||||
|
||||
# if automation is enabled we create more transfinite curves
|
||||
if mr_obj.UseAutomation:
|
||||
if idx_dict["Vertex"]:
|
||||
Console.PrintError( ("Transfinite surface automation in {} cannot not work with vertex selection.\n").format(mr_obj.Name))
|
||||
continue
|
||||
|
||||
definition = tft.TFCurveDefinition.from_tfcurve_obj(mr_obj)
|
||||
edge_sets = tft.get_automatic_transfinite_edge_sets(self.part_obj.Shape, elements, transfinite_curve_list, definition)
|
||||
for auto_definition, auto_edges in edge_sets.items():
|
||||
prefix = auto_definition.tag_prefix()
|
||||
setting = auto_definition.to_gmshtools_setting()
|
||||
setting["tag"] = ",".join(prefix+str(i) for i in auto_edges)
|
||||
|
||||
self.transfinite_curve_settings.append(setting)
|
||||
|
||||
if mr_obj.UseAutomation:
|
||||
definition = tft.TFCurveDefinition.from_tfcurve_obj(mr_obj)
|
||||
try:
|
||||
tft.add_automatic_transfinite_edges_from_faces(edge_map, self.part_obj.Shape, elements, definition)
|
||||
except Exception as e:
|
||||
# error: some user settings are incompatible, abort all transfinite
|
||||
Console.PrintError(str(e))
|
||||
self.transfinite_surface_settings = []
|
||||
return
|
||||
|
||||
else:
|
||||
Console.PrintError(
|
||||
@@ -1082,6 +1066,7 @@ class GmshTools(ObjectTools):
|
||||
"because the reference list is empty.\n".format(mr_obj.Name)
|
||||
)
|
||||
|
||||
|
||||
# transfinite volumes
|
||||
transfinite_volume_list = self._get_definitions_of_type("Fem::MeshTransfiniteVolume")
|
||||
if not transfinite_volume_list:
|
||||
@@ -1123,12 +1108,40 @@ class GmshTools(ObjectTools):
|
||||
|
||||
self.transfinite_volume_settings.append(settings)
|
||||
|
||||
if mr_obj.UseAutomation:
|
||||
|
||||
curve_definition = tft.TFCurveDefinition.from_tfcurve_obj(mr_obj)
|
||||
surf_definition = tft.TFSurfaceDefinition.from_tfsurface_obj(mr_obj)
|
||||
try:
|
||||
tft.add_automatic_transfinite_edges_from_solids(edge_map, self.part_obj.Shape, elements, curve_definition)
|
||||
tft.add_automatic_transfinite_surfaces_from_solids(surface_map, self.part_obj.Shape, elements, surf_definition)
|
||||
except Exception as e:
|
||||
# error: some user settings are incompatible, abort all transfinite
|
||||
Console.PrintError(str(e))
|
||||
self.transfinite_surface_settings = []
|
||||
self.transfinite_volume_settings = []
|
||||
return
|
||||
|
||||
else:
|
||||
Console.PrintError(
|
||||
"The transfinite volume {} is not used to create the mesh "
|
||||
"because the reference list is empty.\n".format(mr_obj.Name)
|
||||
)
|
||||
|
||||
# with all automation steps handled we create the transfinite curves settings!
|
||||
definition_map = tft.map_to_definitions(edge_map, self.part_obj.Shape)
|
||||
for definition, edges in definition_map.items():
|
||||
prefix = definition.tag_prefix()
|
||||
setting = definition.to_gmshtools_setting()
|
||||
setting["tag"] = ",".join(prefix+str(i) for i in edges)
|
||||
self.transfinite_curve_settings.append(setting)
|
||||
|
||||
# and remaining transfinite surface settings!
|
||||
definition_map = tft.map_to_definitions(surface_map, self.part_obj.Shape, only_by_creator = tft.Creation.AutomaticVolume)
|
||||
for definition, surfaces in definition_map.items():
|
||||
setting = definition.to_gmshtools_setting()
|
||||
setting["surfaces"] = ",".join(prefix+str(i) for i in surfaces)
|
||||
self.transfinite_surface_settings.append(setting)
|
||||
|
||||
|
||||
def write_groups(self, geo):
|
||||
@@ -1241,7 +1254,7 @@ class GmshTools(ObjectTools):
|
||||
geo.write( f' = {{ {setting["nodes"]} }}' )
|
||||
if "orientation" in setting:
|
||||
geo.write(f' {setting["orientation"]}')
|
||||
if "recombine" in setting:
|
||||
if "recombine" in setting and setting["recombine"]:
|
||||
geo.write(";\n")
|
||||
geo.write(f'Recombine Surface {{ {setting["surfaces"]} }}')
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ __url__ = "https://www.freecad.org"
|
||||
# \brief Tools to help automating application of gmsh transfinite meshes
|
||||
|
||||
import FreeCAD
|
||||
from FreeCAD import Console
|
||||
import Part
|
||||
from femtools import geomtools
|
||||
|
||||
from enum import Enum, auto
|
||||
from dataclasses import dataclass, field
|
||||
@@ -68,11 +70,29 @@ class TFCurveDefinition:
|
||||
return TFCurveDefinition(obj.Nodes, obj.Coefficient,
|
||||
obj.Distribution, obj.Invert)
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TFSurfaceDefinition:
|
||||
Recombine: bool = False
|
||||
Orientation: str = "Left"
|
||||
|
||||
def to_gmshtools_setting(self):
|
||||
|
||||
settings = {}
|
||||
settings["recombine"] = self.Recombine
|
||||
settings["orientation"] = self.Orientation
|
||||
|
||||
return settings
|
||||
|
||||
@staticmethod
|
||||
def from_tfsurface_obj(obj):
|
||||
return TFSurfaceDefinition(obj.Recombine, obj.TriangleOrientation)
|
||||
|
||||
|
||||
class Creation(Enum):
|
||||
Undefined = auto()
|
||||
User = auto()
|
||||
Automatic = auto()
|
||||
Undefined = auto()
|
||||
User = auto() # user created
|
||||
AutomaticSurface = auto() # created by surface automation
|
||||
AutomaticVolume = auto() # created by volume automation
|
||||
|
||||
@dataclass
|
||||
class EdgeData:
|
||||
@@ -87,18 +107,35 @@ class EdgeData:
|
||||
|
||||
@property
|
||||
def IsAutomatic(self):
|
||||
return self.Creation == Creation.Automatic
|
||||
return (self.Creation == Creation.AutomaticSurface or
|
||||
self.Creation == Creation.AutomaticVolume)
|
||||
|
||||
@dataclass
|
||||
class EdgeKey:
|
||||
class FaceData:
|
||||
|
||||
Edge: object
|
||||
Creation: Creation = Creation.Undefined
|
||||
Data: TFSurfaceDefinition = field(default_factory=TFSurfaceDefinition)
|
||||
|
||||
|
||||
@property
|
||||
def IsDefined(self):
|
||||
return self.Creation != Creation.Undefined
|
||||
|
||||
@property
|
||||
def IsAutomatic(self):
|
||||
# Automatic Surface is invalid for face, as it can only be created automaticall by volume automation
|
||||
return self.Creation == Creation.AutomaticVolume
|
||||
|
||||
@dataclass
|
||||
class Key:
|
||||
|
||||
Shape: object
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.Edge.isSame(other.Edge)
|
||||
return self.Shape.isSame(other.Shape)
|
||||
|
||||
def __hash__(self):
|
||||
return self.Edge.hashCode()
|
||||
return self.Shape.hashCode()
|
||||
|
||||
|
||||
def _get_common_faces(face_list_1, face_list_2):
|
||||
@@ -146,7 +183,7 @@ def _get_opposing_edge(face, edge):
|
||||
raise Exception("Could not find opposing edge")
|
||||
|
||||
|
||||
def _propagate_edge(edges_map, shape, faces, edge_key, origin_face):
|
||||
def _propagate_edge(edges_map, shape, faces, edge_key, origin_face, creator):
|
||||
# Propagates the edge values through the map
|
||||
#
|
||||
# edges_map: The map of all edge keys to edge data
|
||||
@@ -156,7 +193,7 @@ def _propagate_edge(edges_map, shape, faces, edge_key, origin_face):
|
||||
# origin_face: The face the propagated edge was itself propagated from (to not go backwards)
|
||||
|
||||
# 1. Get all faces the edge belongs to (from user provided list)
|
||||
edge_faces = shape.ancestorsOfType(edge_key.Edge, Part.Face)
|
||||
edge_faces = shape.ancestorsOfType(edge_key.Shape, Part.Face)
|
||||
propagate_faces = _get_common_faces(edge_faces, faces)
|
||||
|
||||
# 3. Find opposing edges and apply value if not set yet
|
||||
@@ -166,7 +203,7 @@ def _propagate_edge(edges_map, shape, faces, edge_key, origin_face):
|
||||
if origin_face and face.isSame(origin_face):
|
||||
continue
|
||||
|
||||
opposite = EdgeKey(_get_opposing_edge(face, edge_key.Edge))
|
||||
opposite = Key(_get_opposing_edge(face, edge_key.Shape))
|
||||
if edges_map[opposite].IsDefined:
|
||||
# check if compatibel, and raise error if not
|
||||
if edges_map[opposite].Data.Nodes != edges_map[edge_key].Data.Nodes:
|
||||
@@ -178,80 +215,212 @@ def _propagate_edge(edges_map, shape, faces, edge_key, origin_face):
|
||||
else:
|
||||
# transfer the data as automatic definition
|
||||
edges_map[opposite].Data = edges_map[edge_key].Data
|
||||
edges_map[opposite].Creation = Creation.Automatic
|
||||
edges_map[opposite].Creation = creator
|
||||
|
||||
# propagate the opposite edge further
|
||||
_propagate_edge(edges_map, shape, faces, opposite, face)
|
||||
_propagate_edge(edges_map, shape, faces, opposite, face, creator)
|
||||
|
||||
|
||||
def _get_automatic_transfinite_edges(shape, faces, tf_curve_objs, auto_curve_data):
|
||||
# Returns a map from EdgeKey to EdgeData for all edges that require transfinite curve definitions
|
||||
def _get_reference_elements(shape, obj):
|
||||
# basically a copy of gmshtools get_reference_elements
|
||||
# needed because of the search in reference subshape part
|
||||
|
||||
elements = set()
|
||||
for sub in obj.References:
|
||||
# check if the shape of the mesh refinements
|
||||
# is an element of the Part to mesh
|
||||
# if not try to find the element in the shape to mesh
|
||||
search_ele_in_shape_to_mesh = False
|
||||
if not shape.isSame(sub[0].Shape):
|
||||
Console.PrintLog(
|
||||
"One element of the mesh refinement {} is "
|
||||
"not an element of the Part to mesh.\n"
|
||||
"But we are going to try to find it in "
|
||||
"the Shape to mesh :-)\n".format(obj.Name)
|
||||
)
|
||||
search_ele_in_shape_to_mesh = True
|
||||
|
||||
for element in sub[1]:
|
||||
if search_ele_in_shape_to_mesh:
|
||||
# we're going to try to find the element in the
|
||||
# Shape to mesh and use the found element as elems
|
||||
# the method getElement(element)
|
||||
# does not return Solid elements
|
||||
ele_shape = geomtools.get_element(sub[0], element)
|
||||
found_element = geomtools.find_element_in_shape(
|
||||
shape, ele_shape
|
||||
)
|
||||
if found_element:
|
||||
element = found_element
|
||||
else:
|
||||
Console.PrintError(
|
||||
"One element of the mesh refinement {} could not be found "
|
||||
"in the Part to mesh. It will be ignored.\n".format(
|
||||
obj.Name
|
||||
)
|
||||
)
|
||||
elements.add(element)
|
||||
|
||||
return elements
|
||||
|
||||
def _get_automatic_transfinite_edges(edge_map, shape, faces, auto_curve_data, creator):
|
||||
# Returns a map from Key to EdgeData for all edges that require transfinite curve definitions
|
||||
#
|
||||
# shape: The Part shape object all the faces belong to
|
||||
# faces: A list of faces which shall be transfinite
|
||||
# tf_curve_objects: The transfinite curve document objects governing the faces
|
||||
# auto_curve_data: A TFCurveDefinition object which will be applied on all undefined edges
|
||||
|
||||
# 1. get the full edge map (careful to not doulbe add edges)
|
||||
edges_map = {}
|
||||
# 1. add all new edges not yet defined (careful to not doulbe add edges)
|
||||
for face in faces:
|
||||
for edge in face.Edges:
|
||||
if not EdgeKey(edge) in edges_map:
|
||||
edges_map[EdgeKey(edge)] = EdgeData()
|
||||
if not Key(edge) in edge_map:
|
||||
edge_map[Key(edge)] = EdgeData()
|
||||
|
||||
# 2. Setup the user defined values
|
||||
# 2. get all already defined edges
|
||||
user_edges = set()
|
||||
surf_edges = set()
|
||||
vol_edges = set()
|
||||
for key, data in edge_map.items():
|
||||
|
||||
if data.Creation == Creation.User:
|
||||
user_edges.add(key)
|
||||
elif data.Creation == Creation.AutomaticSurface:
|
||||
surf_edges.add(key)
|
||||
elif data.Creation == Creation.AutomaticVolume:
|
||||
vol_edges.add(key)
|
||||
|
||||
# 3. Propagate all user defined values through the map
|
||||
for user_edge in user_edges:
|
||||
_propagate_edge(edge_map, shape, faces, user_edge, None, creator)
|
||||
|
||||
# 3. Propagate all already auto-surface defined values through the map (lower prio then user defined)
|
||||
# Requried when there are multiple transfinite surface objects with automation
|
||||
for surf_edge in surf_edges:
|
||||
_propagate_edge(edge_map, shape, faces, surf_edge, None, creator)
|
||||
|
||||
# 4. Propagate all already auto-surface defined values through the map (lower prio then user defined)
|
||||
# Requried when there are multiple transfinite surface objects with automation
|
||||
for vol_edge in vol_edges:
|
||||
_propagate_edge(edge_map, shape, faces, vol_edge, None, creator)
|
||||
|
||||
# 5. Check if we have further undefined edges, and use the default data on them
|
||||
for key, data in edge_map.items():
|
||||
if not data.IsDefined:
|
||||
data.Data = auto_curve_data
|
||||
data.Creation = creator
|
||||
|
||||
|
||||
def setup_transfinite_edge_map(shape, tf_curve_objs):
|
||||
# builds the initial edge map with user defined edges
|
||||
|
||||
edges_map = {}
|
||||
for tf in tf_curve_objs:
|
||||
|
||||
if tf.Suppressed:
|
||||
continue
|
||||
|
||||
data = TFCurveDefinition(tf.Nodes, tf.Coefficient, tf.Distribution, tf.Invert)
|
||||
data = TFCurveDefinition.from_tfcurve_obj(tf)
|
||||
|
||||
for ref in tf.References:
|
||||
obj = ref[0]
|
||||
for sub in ref[1]:
|
||||
if not "Edge" in sub:
|
||||
continue
|
||||
print("handle refs")
|
||||
for ref in _get_reference_elements(shape, tf):
|
||||
print("ref element: ", ref)
|
||||
ref_shape = shape.getElement(ref)
|
||||
if ref_shape.ShapeType != "Edge":
|
||||
continue
|
||||
|
||||
key = EdgeKey(obj.getSubObject(sub))
|
||||
edges_map[key] = EdgeData(Creation=Creation.User, Data=data)
|
||||
user_edges.add(key)
|
||||
key = Key(ref_shape)
|
||||
if key in edges_map:
|
||||
# double definition: ignore latest
|
||||
Console.PrintError( (f"The transfinite curve {tf.Label} redefines already setup edges. Those definitions are ignored.\n") )
|
||||
continue
|
||||
|
||||
# 3. Propagate all user defined values through the map
|
||||
for user_edge in user_edges:
|
||||
_propagate_edge(edges_map, shape, faces, user_edge, None)
|
||||
|
||||
# 4. Check if we have further undefined edges, and use the default data on them
|
||||
for key, data in edges_map.items():
|
||||
if not data.IsDefined:
|
||||
data.Data = auto_curve_data
|
||||
data.Creation = Creation.Automatic
|
||||
edges_map[key] = EdgeData(Creation=Creation.User, Data=data)
|
||||
|
||||
return edges_map
|
||||
|
||||
def get_automatic_transfinite_edge_sets(shape, faces, tf_curve_objs, auto_curve_data):
|
||||
# returns map of TFCurveDefinition to [EdgeID]
|
||||
def setup_transfinite_surface_map(shape, tf_surface_objs):
|
||||
# builds the initial surface map with user defined surfaces
|
||||
|
||||
# faces names to faces shape
|
||||
face_shapes = [shape.getElement(name) for name in faces]
|
||||
faces_map = {}
|
||||
for tf in tf_surface_objs:
|
||||
|
||||
# get transfinite data for all edges
|
||||
edge_map = _get_automatic_transfinite_edges(shape, face_shapes, tf_curve_objs, auto_curve_data)
|
||||
if tf.Suppressed:
|
||||
continue
|
||||
|
||||
data = TFSurfaceDefinition.from_tfsurface_obj(tf)
|
||||
|
||||
for ref in _get_reference_elements(shape, tf):
|
||||
|
||||
ref_shape = shape.getElement(ref)
|
||||
if ref_shape.ShapeType != "Face":
|
||||
continue
|
||||
|
||||
key = Key(ref_shape)
|
||||
if key in faces_map:
|
||||
# double definition: ignore latest
|
||||
Console.PrintError( (f"The transfinite surface {tf.Label} redefines already setup faces. Those definitions are ignored.\n") )
|
||||
continue
|
||||
|
||||
faces_map[key] = FaceData(Creation=Creation.User, Data=data)
|
||||
|
||||
return faces_map
|
||||
|
||||
def map_to_definitions(edge_map, shape, only_by_creator=None):
|
||||
|
||||
# convert into individual curve definitions
|
||||
result = {}
|
||||
for edge, edge_data in edge_map.items():
|
||||
|
||||
if not edge_data.IsAutomatic:
|
||||
if only_by_creator and edge_data.Creation != only_by_creator:
|
||||
continue
|
||||
|
||||
if not edge_data.Data in result:
|
||||
result[edge_data.Data] = []
|
||||
|
||||
name, idx = shape.findSubShape(edge.Edge)
|
||||
name, idx = shape.findSubShape(edge.Shape)
|
||||
result[edge_data.Data].append(idx)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def add_automatic_transfinite_edges_from_faces(edge_map, shape, faces, auto_curve_data):
|
||||
# adds automatic transfinite curves to the edge map based on the faces that should be automatically
|
||||
# extended
|
||||
|
||||
# faces names to faces shape
|
||||
face_shapes = [shape.getElement(name) for name in faces]
|
||||
|
||||
# get transfinite data for all edges
|
||||
_get_automatic_transfinite_edges(edge_map, shape, face_shapes, auto_curve_data, Creation.AutomaticSurface)
|
||||
|
||||
|
||||
def add_automatic_transfinite_edges_from_solids(edge_map, shape, solids, auto_curve_data):
|
||||
|
||||
# solid names to solids
|
||||
solid_shapes = [shape.Solids[int(e.lstrip("Solid")) - 1] for e in solids]
|
||||
|
||||
# faces names to faces shape
|
||||
face_shapes = []
|
||||
for solid in solid_shapes:
|
||||
face_shapes += solid.Faces
|
||||
|
||||
# get transfinite data for all edges
|
||||
_get_automatic_transfinite_edges(edge_map, shape, face_shapes, auto_curve_data, Creation.AutomaticVolume)
|
||||
|
||||
def add_automatic_transfinite_surfaces_from_solids(surface_map, shape, solids, auto_surface_data):
|
||||
|
||||
# solid names to solids
|
||||
solid_shapes = [shape.Solids[int(e.lstrip("Solid")) - 1] for e in solids]
|
||||
|
||||
# faces names to faces shape
|
||||
face_shapes = []
|
||||
for solid in solid_shapes:
|
||||
face_shapes += solid.Faces
|
||||
|
||||
# check if faces are not defined, and add them if so
|
||||
for face in face_shapes:
|
||||
key = Key(face)
|
||||
if not key in surface_map:
|
||||
surface_map[key] = FaceData(Creation=Creation.AutomaticVolume, Data=auto_surface_data)
|
||||
|
||||
|
||||
@@ -48,6 +48,55 @@ class MeshTransfiniteVolume(base_femmeshelement.BaseFemMeshElement):
|
||||
group="Transfinite",
|
||||
doc="Creates mixt element types when the bounding surfaces mix quads and triangels",
|
||||
value=False,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="TriangleOrientation",
|
||||
group="Automation",
|
||||
doc="Define how the triangles are oriented within the transfinite mesh (if not recombined) for surfaces that are automatically converted to be transfinite",
|
||||
value=["Left", "Right", "AlternateRight", "AlternateLeft"],
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="Recombine",
|
||||
group="Automation",
|
||||
doc="Define if the triangles on a surface shall be recombined into quads, if that surface is automatically converted to be transfinite",
|
||||
value=False,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="UseAutomation",
|
||||
group="Automation",
|
||||
doc="Enables the automatic application of transfinite curve and surface definitions on all yet undefined edges",
|
||||
value=False
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyInteger",
|
||||
name="Nodes",
|
||||
group="Automation",
|
||||
doc="The number of nodes distributed over each edge that is automatically converted to a transfinite curve",
|
||||
value=10,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyFloat",
|
||||
name="Coefficient",
|
||||
group="Automation",
|
||||
doc="The coefficient used by distributon algorithms (except constant) for automatically converted transfinite curves",
|
||||
value=1.2,
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyEnumeration",
|
||||
name="Distribution",
|
||||
group="Automation",
|
||||
doc="The algorithm used to distribute the nodes on the edge for automatically converted transfinite curves",
|
||||
value=["Constant", "Bump", "Progression"],
|
||||
),
|
||||
_PropHelper(
|
||||
type="App::PropertyBool",
|
||||
name="Invert",
|
||||
group="Automation",
|
||||
doc="Inverts the direction of of the non-constant distributions",
|
||||
value=False,
|
||||
),]
|
||||
|
||||
return super()._get_properties() + props
|
||||
|
||||
@@ -133,6 +133,7 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel):
|
||||
@QtCore.Slot(bool)
|
||||
def recombineChanged(self, value):
|
||||
self.obj.Recombine = value
|
||||
self.parameter_widget.Orientation.setDisabled(self.obj.Recombine)
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def orientationChanged(self, value):
|
||||
|
||||
@@ -93,6 +93,36 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel):
|
||||
ui.Mixed.setChecked(self.obj.MixedElements)
|
||||
ui.Mixed.toggled.connect(self.mixedChanged)
|
||||
|
||||
# automation
|
||||
|
||||
ui.Recombine.setChecked(self.obj.Recombine)
|
||||
ui.Recombine.toggled.connect(self.recombineChanged)
|
||||
|
||||
ui.Orientation.addItems(self.obj.getEnumerationsOfProperty("TriangleOrientation"))
|
||||
ui.Orientation.setCurrentIndex(ui.Orientation.findData(self.obj.TriangleOrientation))
|
||||
ui.Orientation.currentIndexChanged.connect(self.orientationChanged)
|
||||
ui.Orientation.setDisabled(self.obj.Recombine)
|
||||
|
||||
ui.Automation.setChecked(self.obj.UseAutomation)
|
||||
ui.Automation.toggled.connect(self.automationChanged)
|
||||
|
||||
ui.Nodes.setValue(self.obj.Nodes)
|
||||
FreeCADGui.ExpressionBinding(ui.Nodes).bind(self.obj, "Nodes")
|
||||
ui.Nodes.valueChanged.connect(self.nodesChanged)
|
||||
|
||||
ui.Coefficient.setValue(self.obj.Coefficient)
|
||||
FreeCADGui.ExpressionBinding(ui.Coefficient).bind(self.obj, "Coefficient")
|
||||
ui.Coefficient.valueChanged.connect(self.coefficientChanged)
|
||||
|
||||
match self.obj.Distribution:
|
||||
case "Constant":
|
||||
ui.Distribution.setCurrentIndex(0)
|
||||
case "Bump":
|
||||
ui.Distribution.setCurrentIndex(1+int(self.obj.Invert))
|
||||
case "Progression":
|
||||
ui.Distribution.setCurrentIndex(3+int(self.obj.Invert))
|
||||
|
||||
ui.Distribution.currentIndexChanged.connect(self.distributionChanged)
|
||||
|
||||
def accept(self):
|
||||
self.obj.References = self.selection_widget.references
|
||||
@@ -106,3 +136,43 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel):
|
||||
@QtCore.Slot(bool)
|
||||
def mixedChanged(self, value):
|
||||
self.obj.MixedElements = value
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def recombineChanged(self, value):
|
||||
self.obj.Recombine = value
|
||||
self.parameter_widget.Orientation.setDisabled(self.obj.Recombine)
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def orientationChanged(self, value):
|
||||
self.obj.TriangleOrientation = value
|
||||
|
||||
@QtCore.Slot(bool)
|
||||
def automationChanged(self, value):
|
||||
self.obj.UseAutomation = value
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def nodesChanged(self, value):
|
||||
self.obj.Nodes = int(value)
|
||||
|
||||
@QtCore.Slot(int)
|
||||
def distributionChanged(self, value):
|
||||
|
||||
match value:
|
||||
case 0:
|
||||
self.obj.Distribution = "Constant"
|
||||
case 1:
|
||||
self.obj.Distribution = "Bump"
|
||||
self.obj.Invert = False
|
||||
case 2:
|
||||
self.obj.Distribution = "Bump"
|
||||
self.obj.Invert = True
|
||||
case 3:
|
||||
self.obj.Distribution = "Progression"
|
||||
self.obj.Invert = False
|
||||
case 4:
|
||||
self.obj.Distribution = "Progression"
|
||||
self.obj.Invert = True
|
||||
|
||||
@QtCore.Slot(float)
|
||||
def coefficientChanged(self, value):
|
||||
self.obj.Coefficient = float(value)
|
||||
|
||||
Reference in New Issue
Block a user