FEM: Add automatic transfinite volume algorithm

This commit is contained in:
Stefan Tröger
2025-11-18 17:35:35 +01:00
parent 97204f9cfe
commit 9d5167de90
7 changed files with 545 additions and 101 deletions
@@ -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>
+63 -50
View File
@@ -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"]} }}')
+215 -46
View File
@@ -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)
+49
View File
@@ -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)