diff --git a/src/Base/UnitsSchemaInternal.cpp b/src/Base/UnitsSchemaInternal.cpp index 4b141fd247..c17cbbe99e 100644 --- a/src/Base/UnitsSchemaInternal.cpp +++ b/src/Base/UnitsSchemaInternal.cpp @@ -551,7 +551,7 @@ UnitsSchemaInternal::schemaTranslate(const Quantity& quant, double& factor, QStr } } else if (unit == Unit::DissipationRate) { - unitString = QString::fromLatin1("m^2/s^3"); + unitString = QString::fromLatin1("W/kg"); factor = 1e6; } else if (unit == Unit::InverseLength) { diff --git a/src/Base/UnitsSchemaMKS.cpp b/src/Base/UnitsSchemaMKS.cpp index 96034a9495..5d921e8166 100644 --- a/src/Base/UnitsSchemaMKS.cpp +++ b/src/Base/UnitsSchemaMKS.cpp @@ -538,7 +538,7 @@ QString UnitsSchemaMKS::schemaTranslate(const Quantity& quant, double& factor, Q } } else if (unit == Unit::DissipationRate) { - unitString = QString::fromLatin1("m^2/s^3"); + unitString = QString::fromLatin1("W/kg"); factor = 1e6; } else if (unit == Unit::InverseLength) { diff --git a/src/Mod/Fem/Gui/Resources/ui/BodyHeatSource.ui b/src/Mod/Fem/Gui/Resources/ui/BodyHeatSource.ui index e263d118a6..50a3fd7461 100644 --- a/src/Mod/Fem/Gui/Resources/ui/BodyHeatSource.ui +++ b/src/Mod/Fem/Gui/Resources/ui/BodyHeatSource.ui @@ -13,56 +13,115 @@ Analysis feature properties - + - - - Body heat in W/kg: + + + + 0 + 0 + - - - - - - Qt::Horizontal + + - - - 130 - 19 - - - - - - - - true - - - - 100 - 20 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - 0.000000000000000 - - - 1000000000000000000000.000000000000000 - - - 50.000000000000000 - - - 0.000000000000000 + + Heat Source + + + + + Mode: + + + + + + + + + + Total Power: + + + + + + + true + + + W + + + + 100 + 20 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + 0.000000000000000 + + + 1000000000000000000000.000000000000000 + + + 50.000000000000000 + + + 0.000000000000000 + + + + + + + Dissipation Rate: + + + + + + + true + + + W/kg + + + + 100 + 20 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + 0.000000000000000 + + + 1000000000000000000000.000000000000000 + + + 50.000000000000000 + + + 0.000000000000000 + + + + diff --git a/src/Mod/Fem/femobjects/constraint_bodyheatsource.py b/src/Mod/Fem/femobjects/constraint_bodyheatsource.py index 92c9cc0aac..ac1f4d23ac 100644 --- a/src/Mod/Fem/femobjects/constraint_bodyheatsource.py +++ b/src/Mod/Fem/femobjects/constraint_bodyheatsource.py @@ -1,6 +1,7 @@ # *************************************************************************** # * Copyright (c) 2017 Markus Hovorka * # * Copyright (c) 2020 Bernd Hahnebach * +# * Copyright (c) 2024 Mario Passaglia * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -23,15 +24,19 @@ # *************************************************************************** __title__ = "FreeCAD FEM constraint body heat source document object" -__author__ = "Markus Hovorka, Bernd Hahnebach" +__author__ = "Markus Hovorka, Bernd Hahnebach, Mario Passaglia" __url__ = "https://www.freecad.org" ## @package constraint_bodyheatsource # \ingroup FEM # \brief constraint body heat source object +import FreeCAD + from . import base_fempythonobject +_PropHelper = base_fempythonobject._PropHelper + class ConstraintBodyHeatSource(base_fempythonobject.BaseFemPythonObject): @@ -39,18 +44,56 @@ class ConstraintBodyHeatSource(base_fempythonobject.BaseFemPythonObject): def __init__(self, obj): super(ConstraintBodyHeatSource, self).__init__(obj) - self.add_properties(obj) + + for prop in self._get_properties(): + prop.add_to_object(obj) + + + def _get_properties(self): + prop = [] + + prop.append(_PropHelper( + type = "App::PropertyDissipationRate", + name = "DissipationRate", + group = "Constraint Body Heat Source", + doc = "Power dissipated per unit mass", + value = "0 W/kg" + ) + ) + prop.append(_PropHelper( + type = "App::PropertyPower", + name = "TotalPower", + group = "Constraint Body Heat Source", + doc = "Total power dissipated", + value = "0 W" + ) + ) + prop.append(_PropHelper( + type = "App::PropertyEnumeration", + name = "Mode", + group = "Constraint Body Heat Source", + doc = "Switch quantity input mode", + value = ["Dissipation Rate", "Total Power"] + ) + ) + + return prop + def onDocumentRestored(self, obj): - self.add_properties(obj) + # update old project with new properties + for prop in self._get_properties(): + try: + obj.getPropertyByName(prop.name) + except: + prop.add_to_object(obj) - def add_properties(self, obj): - if not hasattr(obj, "HeatSource"): - obj.addProperty( - "App::PropertyFloat", - "HeatSource", - "Base", - "Body heat source" - ) - obj.setPropertyStatus("HeatSource", "LockDynamic") - obj.HeatSource = 0.0 + # migrate old HeatSource property + try: + value = obj.getPropertyByName("HeatSource") + obj.DissipationRate = FreeCAD.Units.Quantity(value, "W/kg") + obj.Mode = "Dissipation Rate" + obj.setPropertyStatus("HeatSource", "-LockDynamic") + obj.removeProperty("HeatSource") + except: + pass diff --git a/src/Mod/Fem/femsolver/calculix/write_constraint_bodyheatsource.py b/src/Mod/Fem/femsolver/calculix/write_constraint_bodyheatsource.py index 075209de84..16232acc55 100644 --- a/src/Mod/Fem/femsolver/calculix/write_constraint_bodyheatsource.py +++ b/src/Mod/Fem/femsolver/calculix/write_constraint_bodyheatsource.py @@ -25,8 +25,8 @@ __title__ = "FreeCAD FEM calculix constraint body heat source" __author__ = "Mario Passaglia" __url__ = "https://www.freecad.org" - import FreeCAD +import itertools def get_analysis_types(): @@ -70,22 +70,29 @@ def write_constraint(f, femobj, bodyheatsource_obj, ccxwriter): # floats read from ccx should use {:.13G}, see comment in writer module # search referenced material - ref = bodyheatsource_obj.References + ref = bodyheatsource_obj.References[0] + ref_feat = ref[0] + ref_sub_obj = ref[1][0] density = None for mat in ccxwriter.member.mats_linear: - for mat_ref in mat["Object"].References: - if mat_ref[0] == ref[0][0]: - density = FreeCAD.Units.Quantity(mat["Object"].Material["Density"]) - break + mat_ref = [*itertools.chain(*[itertools.product([i[0]],i[1]) for i in mat["Object"].References])] + if (ref_feat, ref_sub_obj) in mat_ref: + density = FreeCAD.Units.Quantity(mat["Object"].Material["Density"]) + break if not density: # search material without references for mat in ccxwriter.member.mats_linear: if not mat["Object"].References: density = FreeCAD.Units.Quantity(mat["Object"].Material["Density"]) + break - # get some data from the bodyheatsource_obj (is in power per unit mass) - heat = FreeCAD.Units.Quantity(bodyheatsource_obj.HeatSource, "m^2/s^3") * density + # get data from the bodyheatsource_obj (DissipationRate is in power per unit mass) + if bodyheatsource_obj.Mode == "Dissipation Rate": + heat = bodyheatsource_obj.DissipationRate * density + elif bodyheatsource_obj.Mode == "Total Power": + volume = ref_feat.getSubObject(ref_sub_obj).Volume + heat = bodyheatsource_obj.TotalPower / FreeCAD.Units.Quantity(volume, "mm^3") # write to file f.write("*DFLUX\n") f.write( diff --git a/src/Mod/Fem/femsolver/elmer/equations/heat_writer.py b/src/Mod/Fem/femsolver/elmer/equations/heat_writer.py index 6478971840..7fa9231269 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/heat_writer.py +++ b/src/Mod/Fem/femsolver/elmer/equations/heat_writer.py @@ -30,6 +30,9 @@ __url__ = "https://www.freecad.org" ## \addtogroup FEM # @{ +import itertools +import FreeCAD + from .. import sifio from .. import writer as general_writer from femtools import membertools @@ -138,7 +141,29 @@ class Heatwriter: self.write.handled(tempObj) def _outputHeatBodyForce(self, obj, name): - heatSource = self.write.getFromUi(obj.HeatSource, "W/kg", "L^2*T^-3") + if obj.Mode == "Dissipation Rate": + heatSource = obj.DissipationRate.getValueAs("W/kg").Value + + elif obj.Mode == "Total Power": + ref = obj.References[0] + ref_feat = ref[0] + ref_sub_obj = ref[1][0] + density = None + for mat in self.write.getMember("App::MaterialObject"): + mat_ref = [*itertools.chain(*[itertools.product([i[0]],i[1]) for i in mat.References])] + if (ref_feat, ref_sub_obj) in mat_ref: + density = FreeCAD.Units.Quantity(mat.Material["Density"]) + break + + if not density: + # search material without references + for mat in self.write.getMember("App::MaterialObject"): + if not mat.References: + density = FreeCAD.Units.Quantity(mat.Material["Density"]) + break + volume = ref_feat.getSubObject(ref_sub_obj).Volume + heatSource = (obj.TotalPower / (density*FreeCAD.Units.Quantity(volume, "mm^3"))).getValueAs("W/kg").Value + if heatSource == 0.0: # a zero heat would break Elmer (division by zero) raise general_writer.WriteError("The body heat source must not be zero!") diff --git a/src/Mod/Fem/femtaskpanels/task_constraint_bodyheatsource.py b/src/Mod/Fem/femtaskpanels/task_constraint_bodyheatsource.py index 9809c57b85..2893073b40 100644 --- a/src/Mod/Fem/femtaskpanels/task_constraint_bodyheatsource.py +++ b/src/Mod/Fem/femtaskpanels/task_constraint_bodyheatsource.py @@ -29,6 +29,8 @@ __url__ = "https://www.freecad.org" # \ingroup FEM # \brief task panel for constraint bodyheatsource object +from PySide import QtCore + import FreeCAD import FreeCADGui @@ -41,15 +43,35 @@ from femtools import membertools class _TaskPanel(object): def __init__(self, obj): - self._obj = obj + self.obj = obj - self._paramWidget = FreeCADGui.PySideUic.loadUi( + self.parameter_widget = FreeCADGui.PySideUic.loadUi( FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/BodyHeatSource.ui") - self._initParamWidget() + + self.init_parameter_widget() + + QtCore.QObject.connect( + self.parameter_widget.qsb_dissipation_rate, + QtCore.SIGNAL("valueChanged(Base::Quantity)"), + self.dissipation_rate_changed + ) + + QtCore.QObject.connect( + self.parameter_widget.qsb_total_power, + QtCore.SIGNAL("valueChanged(Base::Quantity)"), + self.total_power_changed + ) + + QtCore.QObject.connect( + self.parameter_widget.cb_mode, + QtCore.SIGNAL("currentIndexChanged(int)"), + self.mode_changed + ) + # geometry selection widget # start with Solid in list! - self._selectionWidget = selection_widgets.GeometryElementsSelection( + self.selection_widget = selection_widgets.GeometryElementsSelection( obj.References, ["Solid", "Face"], True, @@ -57,7 +79,7 @@ class _TaskPanel(object): ) # form made from param and selection widget - self.form = [self._paramWidget, self._selectionWidget] + self.form = [self.selection_widget, self.parameter_widget] analysis = obj.getParentGroup() self._mesh = None @@ -77,20 +99,22 @@ class _TaskPanel(object): self._part.ViewObject.show() def reject(self): - self._restoreVisibility() + self.restore_visibility() FreeCADGui.ActiveDocument.resetEdit() return True def accept(self): - if self._obj.References != self._selectionWidget.references: - self._obj.References = self._selectionWidget.references - self._applyWidgetChanges() - self._obj.Document.recompute() + self.obj.References = self.selection_widget.references + self.obj.DissipationRate = self.dissipation_rate + self.obj.TotalPower = self.total_power + self.obj.Mode = self.mode + + self.obj.Document.recompute() FreeCADGui.ActiveDocument.resetEdit() - self._restoreVisibility() + self.restore_visibility() return True - def _restoreVisibility(self): + def restore_visibility(self): if self._mesh is not None and self._part is not None: if self._meshVisible: self._mesh.ViewObject.show() @@ -101,21 +125,36 @@ class _TaskPanel(object): else: self._part.ViewObject.hide() - def _initParamWidget(self): - self._paramWidget.bodyheatQSB.setProperty( - 'value', self._obj.HeatSource) - self._paramWidget.bodyheatQSB.setProperty("unit", "W/kg") - FreeCADGui.ExpressionBinding(self._paramWidget.bodyheatQSB).bind(self._obj, "HeatSource") - def _applyWidgetChanges(self): - bodyheat = None - try: - bodyheat = self._paramWidget.bodyheatQSB.property('value').getValueAs("W/kg") - except ValueError: - FreeCAD.Console.PrintMessage( - "Wrong input. Not recognised input: '{}' " - "Body heat has not been set.\n" - .format(self._paramWidget.bodyheatQSB.text()) - ) - if bodyheat is not None: - self._obj.HeatSource = float(bodyheat) + def init_parameter_widget(self): + self.dissipation_rate = self.obj.DissipationRate + self.total_power = self.obj.TotalPower + FreeCADGui.ExpressionBinding(self.parameter_widget.qsb_dissipation_rate)\ + .bind(self.obj, "DissipationRate") + self.parameter_widget.qsb_dissipation_rate.setProperty("value", self.dissipation_rate) + + FreeCADGui.ExpressionBinding(self.parameter_widget.qsb_total_power)\ + .bind(self.obj, "TotalPower") + self.parameter_widget.qsb_total_power.setProperty("value", self.total_power) + + self.mode = self.obj.Mode + self.mode_enum = self.obj.getEnumerationsOfProperty("Mode") + self.parameter_widget.cb_mode.addItems(self.mode_enum) + index = self.mode_enum.index(self.mode) + self.parameter_widget.cb_mode.setCurrentIndex(index) + self.mode_changed(index) + + def dissipation_rate_changed(self, base_quantity_value): + self.dissipation_rate = base_quantity_value + + def total_power_changed(self, base_quantity_value): + self.total_power = base_quantity_value + + def mode_changed(self, index): + self.mode = self.mode_enum[index] + if self.mode == "Dissipation Rate": + self.parameter_widget.qsb_dissipation_rate.setEnabled(True) + self.parameter_widget.qsb_total_power.setEnabled(False) + elif self.mode == "Total Power": + self.parameter_widget.qsb_dissipation_rate.setEnabled(False) + self.parameter_widget.qsb_total_power.setEnabled(True)