From f44af0929fc0791a766976e97c91ed9c20a58474 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Sat, 8 Nov 2025 10:19:03 -0300 Subject: [PATCH] Fem: Add electrostatic concentrated load --- .../Gui/Resources/ui/ElectricChargeDensity.ui | 10 +++++++ src/Mod/Fem/femmesh/meshsetsgetter.py | 13 +++++++++ .../constraint_electricchargedensity.py | 27 +++++++++++++++++++ .../write_constraint_electricchargedensity.py | 14 ++++++++++ .../task_constraint_electricchargedensity.py | 27 ++++++++++++++++--- 5 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/Mod/Fem/Gui/Resources/ui/ElectricChargeDensity.ui b/src/Mod/Fem/Gui/Resources/ui/ElectricChargeDensity.ui index ae6d8e76e7..31a8c4997a 100644 --- a/src/Mod/Fem/Gui/Resources/ui/ElectricChargeDensity.ui +++ b/src/Mod/Fem/Gui/Resources/ui/ElectricChargeDensity.ui @@ -164,6 +164,16 @@ + + + + false + + + Concentrated + + + diff --git a/src/Mod/Fem/femmesh/meshsetsgetter.py b/src/Mod/Fem/femmesh/meshsetsgetter.py index 4eca3211f9..7fdf241152 100644 --- a/src/Mod/Fem/femmesh/meshsetsgetter.py +++ b/src/Mod/Fem/femmesh/meshsetsgetter.py @@ -159,6 +159,7 @@ class MeshSetsGetter: self.get_constraints_temperature_nodes() self.get_constraints_initialtemperature_nodes() self.get_constraints_electrostatic_nodes() + self.get_constraints_electricchargedensity_nodes() # constraints sets with constraint data self.get_constraints_force_nodeloads() @@ -289,6 +290,18 @@ class MeshSetsGetter: self.femmesh, femobj ) + def get_constraints_electricchargedensity_nodes(self): + if not self.member.cons_electricchargedensity: + return + # get nodes + for femobj in self.member.cons_electricchargedensity: + # femobj --> dict, FreeCAD document object is femobj["Object"] + if femobj["Object"].Concentrated: + print_obj_info(femobj["Object"]) + femobj["Nodes"] = meshtools.get_femnodes_by_femobj_with_references( + self.femmesh, femobj + ) + def get_constraints_force_nodeloads(self): if not self.member.cons_force: return diff --git a/src/Mod/Fem/femobjects/constraint_electricchargedensity.py b/src/Mod/Fem/femobjects/constraint_electricchargedensity.py index ed4d0f7634..bf122b4005 100644 --- a/src/Mod/Fem/femobjects/constraint_electricchargedensity.py +++ b/src/Mod/Fem/femobjects/constraint_electricchargedensity.py @@ -31,6 +31,7 @@ __url__ = "https://www.freecad.org" from FreeCAD import Units +from FreeCAD import Base from . import base_fempythonobject @@ -77,6 +78,15 @@ class ConstraintElectricChargeDensity(base_fempythonobject.BaseFemPythonObject): value="0 C", ) ) + prop.append( + _PropHelper( + type="App::PropertyBool", + name="Concentrated", + group="Electric Charge Density", + doc="Use concentrated charges at the nodes", + value=False, + ) + ) prop.append( _PropHelper( type="App::PropertyEnumeration", @@ -89,6 +99,23 @@ class ConstraintElectricChargeDensity(base_fempythonobject.BaseFemPythonObject): return prop + def onDocumentRestored(self, obj): + print("restored", obj.Name) + # update old project with new properties + for prop in self._get_properties(): + try: + obj.getPropertyByName(prop.name) + except Base.PropertyError: + prop.add_to_object(obj) + + def onChanged(self, obj, prop): + print("change", prop) + if prop == "Mode": + if obj.Mode == "Total Source": + obj.setPropertyStatus("Concentrated", "-ReadOnly") + else: + obj.setPropertyStatus("Concentrated", "ReadOnly") + def get_total_source_density(self, obj): """ Calculate density for `Total Source` mode. diff --git a/src/Mod/Fem/femsolver/calculix/write_constraint_electricchargedensity.py b/src/Mod/Fem/femsolver/calculix/write_constraint_electricchargedensity.py index 0b903a70ae..3aaa476da8 100644 --- a/src/Mod/Fem/femsolver/calculix/write_constraint_electricchargedensity.py +++ b/src/Mod/Fem/femsolver/calculix/write_constraint_electricchargedensity.py @@ -43,6 +43,12 @@ def write_meshdata_constraint(f, femobj, den_obj, ccxwriter): if ccxwriter.solver_obj.ElectromagneticMode != "electrostatic": return + if den_obj.Concentrated and den_obj.Mode == "Total Source": + f.write(f"*NSET,NSET={den_obj.Name}\n") + for n in femobj["Nodes"]: + f.write(f"{n},\n") + return + if den_obj.Mode in ["Source", "Total Source"]: f.write(f"*ELSET,ELSET={den_obj.Name}\n") for refs, surf, is_sub_el in femobj["ChargeDensityElements"]: @@ -73,6 +79,14 @@ def write_constraint(f, femobj, den_obj, ccxwriter): if ccxwriter.solver_obj.ElectromagneticMode != "electrostatic": return + if den_obj.Concentrated and den_obj.Mode == "Total Source": + nodes = len(femobj["Nodes"]) + node_charge = den_obj.TotalCharge.getValueAs("C").Value / nodes + f.write("*CFLUX\n") + f.write("{},11,{:.13G}\n".format(den_obj.Name, node_charge)) + f.write("\n") + return + match den_obj.Mode: case "Source": density = den_obj.SourceChargeDensity diff --git a/src/Mod/Fem/femtaskpanels/task_constraint_electricchargedensity.py b/src/Mod/Fem/femtaskpanels/task_constraint_electricchargedensity.py index 1d082454ae..d1b4ae633d 100644 --- a/src/Mod/Fem/femtaskpanels/task_constraint_electricchargedensity.py +++ b/src/Mod/Fem/femtaskpanels/task_constraint_electricchargedensity.py @@ -71,11 +71,16 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel): QtCore.SIGNAL("currentIndexChanged(int)"), self.mode_changed, ) + QtCore.QObject.connect( + self.parameter_widget.ckb_concentrated, + QtCore.SIGNAL("toggled(bool)"), + self.concentrated_changed, + ) # geometry selection widget # start with Solid in list! self.selection_widget = selection_widgets.GeometryElementsSelection( - obj.References, ["Solid", "Face", "Edge"], False, False + obj.References, ["Solid", "Face", "Edge", "Vertex"], False, False ) # form made from param and selection widget @@ -109,6 +114,7 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel): self.obj.InterfaceChargeDensity = self.interface_charge_density self.obj.TotalCharge = self.total_charge self.obj.Mode = self.mode + self.obj.Concentrated = self.concentrated self.selection_widget.finish_selection() self.restore_visibility() @@ -129,6 +135,8 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel): self.source_charge_density = self.obj.SourceChargeDensity self.interface_charge_density = self.obj.InterfaceChargeDensity self.total_charge = self.obj.TotalCharge + self.concentrated = self.obj.Concentrated + FreeCADGui.ExpressionBinding(self.parameter_widget.qsb_source_charge_density).bind( self.obj, "SourceChargeDensity" ) @@ -155,6 +163,10 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel): self.parameter_widget.cb_mode.setCurrentIndex(index) self.mode_changed(index) + self.parameter_widget.ckb_concentrated.setChecked(self.concentrated) + if self.mode == "Total Source": + self.parameter_widget.ckb_concentrated.setVisible(True) + def source_charge_density_changed(self, base_quantity_value): self.source_charge_density = base_quantity_value @@ -166,6 +178,15 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel): def mode_changed(self, index): self.mode = self.mode_enum[index] - if self.mode in ["Total Interface", "Total Source"]: - index = 2 + match self.mode: + case "Total Interface": + index = 2 + self.parameter_widget.ckb_concentrated.setVisible(False) + case "Total Source": + index = 2 + self.parameter_widget.ckb_concentrated.setVisible(True) + self.parameter_widget.sw_mode.setCurrentIndex(index) + + def concentrated_changed(self, value): + self.concentrated = value