From 57b365f18f5e4e167bfa631d82fabdb2fc07e148 Mon Sep 17 00:00:00 2001 From: luzpaz Date: Wed, 22 Feb 2023 13:10:54 +0000 Subject: [PATCH 1/8] Fix misc. typos and whitespace --- src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 2 +- src/Mod/Fem/femexamples/truss_3d_cs_circle_ele_seg3.py | 4 ++-- src/Mod/Material/CMakeLists.txt | 2 +- src/Mod/Start/StartPage/LoadMRU.py | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index cbe1f2c122..81f4271ce5 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -712,7 +712,7 @@ bool ViewProviderFemPostObject::setupPipeline() addAbsoluteField(dset, FieldName); } - m_outline->SetInputData(dset); + m_outline->SetInputData(dset); m_points->SetInputData(dset); m_wireframe->SetInputData(dset); diff --git a/src/Mod/Fem/femexamples/truss_3d_cs_circle_ele_seg3.py b/src/Mod/Fem/femexamples/truss_3d_cs_circle_ele_seg3.py index 50965ccaad..bef00351b1 100644 --- a/src/Mod/Fem/femexamples/truss_3d_cs_circle_ele_seg3.py +++ b/src/Mod/Fem/femexamples/truss_3d_cs_circle_ele_seg3.py @@ -93,7 +93,7 @@ def setup(doc=None, solvertype="ccxtools"): if FreeCAD.GuiUp: load_line.ViewObject.hide() - # commands where generated by Python out of the original Z88 Mesh obj data + # commands were generated by Python out of the original Z88 Mesh obj data v1 = vec(0.0, 2000.0, 0.0) v2 = vec(0.0, 0.0, 0.0) v3 = vec(1000.0, 1000.0, 2000.0) @@ -408,7 +408,7 @@ def setup(doc=None, solvertype="ccxtools"): solver_obj = ObjectsFem.makeSolverZ88(doc, "SolverZ88") else: FreeCAD.Console.PrintWarning( - "Not known or not supported solver type: {}. " + "Unknown or unsupported solver type: {}. " "No solver object was created.\n".format(solvertype) ) if solvertype == "calculix" or solvertype == "ccxtools": diff --git a/src/Mod/Material/CMakeLists.txt b/src/Mod/Material/CMakeLists.txt index 890cb645bb..5ab4131956 100644 --- a/src/Mod/Material/CMakeLists.txt +++ b/src/Mod/Material/CMakeLists.txt @@ -181,7 +181,7 @@ fc_target_copy_resource(MaterialToolsLib ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/Mod/Material ${MaterialTools_Files}) - + fc_target_copy_resource(MaterialIconsLib ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/Mod/Material diff --git a/src/Mod/Start/StartPage/LoadMRU.py b/src/Mod/Start/StartPage/LoadMRU.py index 35db24c7d8..95421f095e 100644 --- a/src/Mod/Start/StartPage/LoadMRU.py +++ b/src/Mod/Start/StartPage/LoadMRU.py @@ -32,4 +32,3 @@ FreeCADGui.loadFile(filename, mod) from StartPage import StartPage StartPage.postStart() - From c00a2d19ec78abf419fa867036a13acf90cb2bae Mon Sep 17 00:00:00 2001 From: Uwe Date: Wed, 22 Feb 2023 21:10:49 +0100 Subject: [PATCH 2/8] [Material] fix major bug of wrong decimals This PR fixes the long-standing major bug that for languages using ',' as decimal separator the material editor was useless. (bug no. 10 in https://forum.freecad.org/viewtopic.php?t=56912) the issue was that the value was handled as text. This PR: - using a spinbox instead if a text edit to modify numbers (has also the advantage to be able to change values by spinning) - sets min/max properly for all number data types - also avoid rounding artifacts when changing Float values --- src/Mod/Material/MaterialEditor.py | 99 ++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/src/Mod/Material/MaterialEditor.py b/src/Mod/Material/MaterialEditor.py index a3b05b6945..1b79561f75 100644 --- a/src/Mod/Material/MaterialEditor.py +++ b/src/Mod/Material/MaterialEditor.py @@ -25,6 +25,7 @@ __author__ = "Yorik van Havre, Bernd Hahnebach" __url__ = "http://www.freecadweb.org" import os +import sys from PySide import QtCore, QtGui, QtSvg import FreeCAD @@ -617,6 +618,8 @@ class MaterialsDelegate(QtGui.QStyledItemDelegate): def __init__(self): "" + self.matproperty = "" + self.Value = "" super(MaterialsDelegate, self).__init__() def createEditor(self, parent, option, index): @@ -635,7 +638,7 @@ class MaterialsDelegate(QtGui.QStyledItemDelegate): row = index.row() PP = group.child(row, 0) - matproperty = PP.text().replace(" ", "") # remove spaces + self.matproperty = PP.text().replace(" ", "") # remove spaces TT = group.child(row, 2) if TT: @@ -644,9 +647,9 @@ class MaterialsDelegate(QtGui.QStyledItemDelegate): Type = "String" VV = group.child(row, 1) - Value = VV.text() + self.Value = VV.text() - editor = matProperWidget(parent, matproperty, Type, Value) + editor = matProperWidget(parent, self.matproperty, Type, self.Value) elif column == 0: if group.text() == "User defined": @@ -676,11 +679,39 @@ class MaterialsDelegate(QtGui.QStyledItemDelegate): lineEdit = editor.children()[1] item.setText(lineEdit.text()) + elif Type == "Float": + # avoid rounding artifacts + inputValue = float('%.6g' % editor.value()) + item.setText(str(inputValue)) + + elif Type == "Quantity": + if not hasattr(FreeCAD.Units, self.matproperty): + FreeCAD.Console.PrintError( + "Error: property '{}' is a quantity but has no unit defined\n" + .format(self.matproperty) + ) + return + # we must use the unit of the input value because the + # "Gui::QuantitySpinBox" uses e.g. for the density always the mm-based unit + # kg/mm^3, also when the input value is in kg/^3. + # E.g. when the input is e.g. "7875 kg/m^3" and we would pass "7875" as rawValue + # and "kg/m^3" as unit, we would get "7875 kg/mm^3" as result. If we try to be + # clever and input "7875e-6" as rawValue and "kg/m^3" as unit we get + # "7875e-6 kg/m^3" as result. If we input "7875e-6" as rawValue and "kg/mm^3" + # as unit we get also "7875e-6 kg/m^3" as result. + if not self.Value: + # for empty (not yet set properties) we use the matproperty unit + unit = getattr(FreeCAD.Units, self.matproperty) + quantity = FreeCAD.Units.Quantity(1, unit) + item.setText(str(editor.value()) + " " + quantity.getUserPreferred()[2]) + else: + # since we checked we have a quantity, we can use split() to get the unit + item.setText(str(editor.value()) + " " + self.Value.split()[1]) + else: super(MaterialsDelegate, self).setEditorData(editor, index) - # ************************************************************************************************ # ************************************************************************************************ def matProperWidget(parent=None, matproperty=None, Type="String", Value=None, @@ -715,9 +746,26 @@ def matProperWidget(parent=None, matproperty=None, Type="String", Value=None, lineEdit.setText(Value) elif Type == "Quantity": - widget = ui.createWidget("Gui::InputField") + # We don't use a Gui::QuantitySpinBox widget because depending on the value, + # the unit might change. For some inputs there is no way to do this right, + # see the comment above in "def setEditorData". Moreover it is error-prone to provide + # a unit as in the Gui::QuantitySpinBox widget because users likely delete the unit or + # change it accidentally. It is therefore better not to display the unit while editing. + widget = ui.createWidget("Gui::DoubleSpinBox") + + if minimum is None: + widget.setMinimum(-1*sys.float_info.max) + else: + widget.setMinimum(minimum) + if maximum is None: + widget.setMaximum(sys.float_info.max) + else: + widget.setMaximum(maximum) + + # we must increase the digits before we can set the value + # 6 is sufficient as some metarial cards use e.g. "0.000011" + widget.setDecimals(6) - # set quantity if possible # for properties with an underscored number (vectorial values), # we must strip the part after the first underscore to obtain the bound unit # since in cardutils.py in def get_material_template @@ -727,30 +775,36 @@ def matProperWidget(parent=None, matproperty=None, Type="String", Value=None, matpropertyNum = matproperty.rstrip('0123456789') matproperty = matpropertyNum - if hasattr(FreeCAD.Units, matproperty): - unit = getattr(FreeCAD.Units, matproperty) - quantity = FreeCAD.Units.Quantity(1, unit) - widget.setProperty("unit", quantity.getUserPreferred()[2]) - else: - FreeCAD.Console.PrintWarning( - "Not known unit for property: {}. Probably the Quantity does not have a unit.\n" - .format(matproperty) - ) + if Value: + widget.setValue(float(Value.split()[0])) elif Type == "Integer": widget = ui.createWidget("Gui::UIntSpinBox") + if minimum is None: + widget.setMinimum(0) + else: + widget.setMinimum(minimum) + if maximum is None: + widget.setMaximum(sys.maxsize) + else: + widget.setMaximum(maximum) elif Type == "Float": - widget = ui.createWidget("Gui::PrefDoubleSpinBox") + widget = ui.createWidget("Gui::DoubleSpinBox") # the magnetic permeability is the parameter for which many decimals matter # the most however, even for this, 6 digits are sufficient widget.setDecimals(6) # for almost all Float parameters of materials a step of 1 would be too large widget.setSingleStep(0.1) if minimum is None: - widget.setProperty("minimum", -1e12) + widget.setMinimum(sys.float_info.min) + else: + widget.setMinimum(minimum) if maximum is None: - widget.setProperty("maximum", 1e12) + widget.setMaximum(sys.float_info.max) + else: + widget.setMaximum(maximum) + widget.setValue(float(Value)) elif Type == "Enumerator": widget = ui.createWidget("Gui::PrefComboBox") @@ -773,15 +827,6 @@ def matProperWidget(parent=None, matproperty=None, Type="String", Value=None, else: widget = QtGui.QLineEdit() - if minimum is not None: - widget.setProperty("minimum", minimum) - if maximum is not None: - widget.setProperty("maximum", maximum) - if stepsize is not None: - widget.setProperty("stepsize", stepsize) - if precision is not None: - widget.setProperty("precision", precision) - widget.setProperty("Type", Type) widget.setParent(parent) From 21369f227f66151f338aff62e09cbeee54d82e33 Mon Sep 17 00:00:00 2001 From: Uwe Date: Wed, 22 Feb 2023 22:53:58 +0100 Subject: [PATCH 3/8] [FEM] fix annoying material selection bug - for some materials, just opening the material dialog and pressing OK to close it without any change resulted in a transient material - for other materials this happened by clicking in the material dialog on a value to edit it, but not changing anything and press OK The fix is to compare the items item by item and also handle the case that only digits of numbers are different --- .../Fem/femtaskpanels/task_material_common.py | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Mod/Fem/femtaskpanels/task_material_common.py b/src/Mod/Fem/femtaskpanels/task_material_common.py index dde7cd7a6d..c4d9603087 100644 --- a/src/Mod/Fem/femtaskpanels/task_material_common.py +++ b/src/Mod/Fem/femtaskpanels/task_material_common.py @@ -260,13 +260,43 @@ class _TaskPanel: "This parameter is not saved in the material data.\n" ) + def isfloat(self, num): + try: + float(num) + return True + except ValueError: + return False + # choose material **************************************************************************** def get_material_card(self, material): for a_mat in self.materials: # check if every item of the current material fits to a known material card # if all items were found we know it is the right card - unmatched_items = set(self.materials[a_mat].items()) ^ set(material.items()) - if len(unmatched_items) == 0: + # we can hereby not simply perform + # set(self.materials[a_mat].items()) ^ set(material.items()) + # because emtries are often identic, just appear in the set in a different order + unmatched_item = False + for item in material.items(): + if item not in self.materials[a_mat].items(): + unmatched_item = True + # often the difference is just a decimal e.g. "120" to "120.0" + # therefore first check if the item name exists + for a_mat_item in self.materials[a_mat].items(): + if item[0] == a_mat_item[0]: + # now check if we have a number value in a unit + if item[1].split() != item[1]: + if not self.isfloat(item[1].split()[0]): + break + if float(item[1].split()[0]) == float(a_mat_item[1].split()[0]): + unmatched_item = False + else: + # it can be a unitless number + if not self.isfloat(item[1]): + break + if float(item[1]) == float(a_mat_item[1]): + unmatched_item = False + break + if not unmatched_item: return a_mat return "" From 486970b90dbe0c57f7e94e917b9cdeebbd6e9c26 Mon Sep 17 00:00:00 2001 From: Uwe Date: Thu, 23 Feb 2023 03:03:06 +0100 Subject: [PATCH 4/8] [FEM] add example for magnetodynamic 2D equation --- src/Mod/Fem/CMakeLists.txt | 1 + .../equation_magnetodynamics_2D_elmer.py | 290 ++++++++++++++++++ src/Mod/Fem/femexamples/manager.py | 2 + 3 files changed, 293 insertions(+) create mode 100644 src/Mod/Fem/femexamples/equation_magnetodynamics_2D_elmer.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index c07b5c5dea..baf51c6e22 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -80,6 +80,7 @@ SET(FemExamples_SRCS femexamples/equation_flow_elmer_2D.py femexamples/equation_flux_elmer.py femexamples/equation_magnetodynamics_elmer.py + femexamples/equation_magnetodynamics_2D_elmer.py femexamples/frequency_beamsimple.py femexamples/manager.py femexamples/material_multiple_bendingbeam_fiveboxes.py diff --git a/src/Mod/Fem/femexamples/equation_magnetodynamics_2D_elmer.py b/src/Mod/Fem/femexamples/equation_magnetodynamics_2D_elmer.py new file mode 100644 index 0000000000..c406601e12 --- /dev/null +++ b/src/Mod/Fem/femexamples/equation_magnetodynamics_2D_elmer.py @@ -0,0 +1,290 @@ +# *************************************************************************** +# * Copyright (c) 2023 Uwe Stöhr * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import sys +import FreeCAD +from FreeCAD import Placement +from FreeCAD import Rotation +from FreeCAD import Vector + +import Draft +import ObjectsFem + +from BOPTools import SplitFeatures +from . import manager +from .manager import get_meshname +from .manager import init_doc + +def get_information(): + return { + "name": "Inductive heating - Elmer 2D", + "meshtype": "solid", + "meshelement": "Tet10", + "constraints": ["current density"], + "solvers": ["elmer"], + "material": "solid", + "equations": ["magnetodynamic"] + } + +def get_explanation(header=""): + return header + """ + +To run the example from Python console use: +from femexamples.equation_magnetodynamics_2D_elmer import setup +setup() + +Magnetodynamic2D equation - Elmer solver + +""" + +def setup(doc=None, solvertype="elmer"): + + # init FreeCAD document + if doc is None: + doc = init_doc() + + # explanation object + # just keep the following line and change text string in get_explanation method + manager.add_explanation_obj(doc, get_explanation(manager.get_header(get_information()))) + + # geometric objects + + # wire defining the insulation area + p1 = Vector(0.0, 0.0, 0.0) + p2 = Vector(40.0, 0, 0.0) + p3 = Vector(40.0, 120.0, 0.0) + p4 = Vector(0.0, 120.0, 0.0) + p5 = Vector(0.0, 100.0, 0.0) + p6 = Vector(25.0, 100.0, 0.0) + p7 = Vector(25.0, 20.0, 0.0) + p8 = Vector(0.0, 20.0, 0.0) + Insulation = Draft.make_wire([p1, p2, p3, p4, p5, p6, p7, p8], closed=True) + Insulation.Label = "Insulation" + Insulation.ViewObject.Visibility = False + + # wire defining the coil volume + p1 = Vector(50.0, -10.0, 0.0) + p2 = Vector(55.0, -10, 0.0) + p3 = Vector(55.0, 110.0, 0.0) + p4 = Vector(50.0, 110.0, 0.0) + Coil = Draft.make_wire([p1, p2, p3, p4], closed=True) + Coil.Label = "Coil" + Coil.ViewObject.Visibility = False + + # wire defining the crucible area + p1 = Vector(0.0, 20.0, 0.0) + p2 = Vector(25.0, 20, 0.0) + p3 = Vector(25.0, 100.0, 0.0) + p4 = Vector(0.0, 100.0, 0.0) + p5 = Vector(0.0, 90.0, 0.0) + p6 = Vector(20.0, 90.0, 0.0) + p7 = Vector(20.0, 30.0, 0.0) + p8 = Vector(0.0, 30.0, 0.0) + Crucible = Draft.make_wire([p1, p2, p3, p4, p5, p6, p7, p8], closed=True) + Crucible.Label = "Crucible" + Crucible.ViewObject.Visibility = False + + # wire defining the powder volume + p1 = Vector(0.0, 30.0, 0.0) + p2 = Vector(20.0, 30.0, 0.0) + p3 = Vector(20.0, 40.0, 0.0) + p4 = Vector(0.0, 40.0, 0.0) + Powder = Draft.make_wire([p1, p2, p3, p4], closed=True) + Powder.Label = "Powder" + Powder.ViewObject.Visibility = False + + # circle defining later the air volume + Air_Circle = Draft.make_circle(134.536) + Air_Circle.Placement = FreeCAD.Placement( + FreeCAD.Vector(0.0, 60.0, 0.0), + FreeCAD.Rotation(0, 0, 0), + FreeCAD.Vector(0, 0, 1), + ) + Air_Circle.Label = "Air_Circle" + Air_Circle.ViewObject.Visibility = False + + # wire to cut the air circle + p1 = Vector(0.0, -100.0, 0.0) + p2 = Vector(-140.0, -100.0, 0.0) + p3 = Vector(-140.0, 200.0, 0.0) + p4 = Vector(0.0, 200.0, 0.0) + Air_Rectangle = Draft.make_wire([p1, p2, p3, p4], closed=True) + Air_Rectangle.Label = "Air_Rectangle" + Air_Rectangle.ViewObject.Visibility = False + + # a link of the Insulation + InsulationLink = doc.addObject("App::Link", "Link_Insulation") + InsulationLink.LinkTransform = True + InsulationLink.LinkedObject = Insulation + InsulationLink.ViewObject.Visibility = False + + # a link of the Coil + CoilLink = doc.addObject("App::Link", "Link_Coil") + CoilLink.LinkTransform = True + CoilLink.LinkedObject = Coil + CoilLink.ViewObject.Visibility = False + + # a link of the Crucible + CrucibleLink = doc.addObject("App::Link", "Link_Crucible") + CrucibleLink.LinkTransform = True + CrucibleLink.LinkedObject = Crucible + CrucibleLink.ViewObject.Visibility = False + + # a link of the Powder + PowderLink = doc.addObject("App::Link", "Link_Powder") + PowderLink.LinkTransform = True + PowderLink.LinkedObject = Powder + PowderLink.ViewObject.Visibility = False + + # fusion of all links + Fusion = doc.addObject("Part::MultiFuse", "Fusion") + Fusion.Shapes = [Air_Rectangle, InsulationLink, CoilLink, CrucibleLink, PowderLink] + Fusion.ViewObject.Visibility = False + + # cut all objects from Air wire to get volume of fluid + Cut = doc.addObject("Part::Cut", "Cut_Air") + Cut.Base = Air_Circle + Cut.Tool = Fusion + Cut.ViewObject.Visibility = False + + # BooleanFregments object to combine cut with rod + BooleanFragments = SplitFeatures.makeBooleanFragments(name="BooleanFragments") + BooleanFragments.Objects = [Insulation, Coil, Crucible, Powder, Cut] + + # set view + doc.recompute() + if FreeCAD.GuiUp: + BooleanFragments.ViewObject.Document.activeView().viewTop() + BooleanFragments.ViewObject.Document.activeView().fitAll() + + # analysis + analysis = ObjectsFem.makeAnalysis(doc, "Analysis") + if FreeCAD.GuiUp: + import FemGui + FemGui.setActiveAnalysis(analysis) + + # solver + if solvertype == "elmer": + solver_obj = ObjectsFem.makeSolverElmer(doc, "SolverElmer") + solver_obj.CoordinateSystem = "Axi Symmetric" + equation_magnetodynamic2D = ObjectsFem.makeEquationMagnetodynamic2D(doc, solver_obj) + equation_magnetodynamic2D.AngularFrequency = "50 kHz" + equation_magnetodynamic2D.CalculateCurrentDensity = True + equation_magnetodynamic2D.CalculateElectricField = True + equation_magnetodynamic2D.CalculateJouleHeating = True + equation_magnetodynamic2D.IsHarmonic = True + else: + FreeCAD.Console.PrintWarning( + "Not known or not supported solver type: {}. " + "No solver object was created.\n".format(solvertype) + ) + return doc + analysis.addObject(solver_obj) + + # materials + + # air around the objects and inside the coil + material_obj = ObjectsFem.makeMaterialFluid(doc, "Air") + mat = material_obj.Material + mat["Name"] = "Air" + mat["Density"] = "1.204 kg/m^3" + mat["ThermalConductivity"] = "0.02587 W/m/K" + mat["ThermalExpansionCoefficient"] = "0.00343/K" + mat["SpecificHeat"] = "1010.00 J/kg/K" + mat["ElectricalConductivity"] = "1e-12 S/m" + mat["RelativePermeability"] = "1.0" + mat["RelativePermittivity"] = "1.00059" + material_obj.Material = mat + material_obj.References = [ + (BooleanFragments, "Face2"), + (BooleanFragments, "Face5"), + (BooleanFragments, "Face6")] + analysis.addObject(material_obj) + + # graphite of the crucible + material_obj = ObjectsFem.makeMaterialSolid(doc, "Material-Crucible") + mat = material_obj.Material + mat["Name"] = "Graphite" + mat["ElectricalConductivity"] = "2e4 S/m" + mat["RelativePermeability"] = "1.0" + material_obj.Material = mat + material_obj.References = [(BooleanFragments, "Face3")] + analysis.addObject(material_obj) + + # insulation of the crucible + material_obj = ObjectsFem.makeMaterialSolid(doc, "Material-Insulation") + mat = material_obj.Material + mat["Name"] = "Insulation" + mat["ElectricalConductivity"] = "2.0e3 S/m" + mat["RelativePermeability"] = "1.0" + material_obj.Material = mat + material_obj.References = [(BooleanFragments, "Face1")] + analysis.addObject(material_obj) + + # mmaterial of powder + material_obj = ObjectsFem.makeMaterialSolid(doc, "Material-Powder") + mat = material_obj.Material + mat["Name"] = "Powder" + mat["ElectricalConductivity"] = "1e4 S/m" + mat["RelativePermeability"] = "1.0" + material_obj.Material = mat + material_obj.References = [(BooleanFragments, "Face4")] + analysis.addObject(material_obj) + + # constraint inlet velocity + CurrentDensity = ObjectsFem.makeConstraintCurrentDensity(doc, "CurrentDensity") + CurrentDensity.References = [(BooleanFragments, "Face2")] + CurrentDensity.CurrentDensity_re_1 = "250000.000 A/m^2" + CurrentDensity.CurrentDensity_re_1_Disabled = False + analysis.addObject(CurrentDensity) + + # mesh + femmesh_obj = analysis.addObject(ObjectsFem.makeMeshGmsh(doc, get_meshname()))[0] + femmesh_obj.Part = BooleanFragments + femmesh_obj.CharacteristicLengthMax = "3 mm" + femmesh_obj.ViewObject.Visibility = False + + # mesh_region + mesh_region = ObjectsFem.makeMeshRegion(doc, femmesh_obj, name="MeshRegion") + mesh_region.CharacteristicLength = "1 mm" + mesh_region.References = [ + (BooleanFragments, "Face1"), + (BooleanFragments, "Face2"), + (BooleanFragments, "Face3"), + (BooleanFragments, "Face4")] + mesh_region.ViewObject.Visibility = False + + # generate the mesh + from femmesh import gmshtools + gmsh_mesh = gmshtools.GmshTools(femmesh_obj, analysis) + try: + error = gmsh_mesh.create_mesh() + except Exception: + error = sys.exc_info()[1] + FreeCAD.Console.PrintError( + "Unexpected error when creating mesh: {}\n" + .format(error) + ) + + doc.recompute() + return doc diff --git a/src/Mod/Fem/femexamples/manager.py b/src/Mod/Fem/femexamples/manager.py index 1b72f456ee..ab0cce0cdf 100644 --- a/src/Mod/Fem/femexamples/manager.py +++ b/src/Mod/Fem/femexamples/manager.py @@ -73,6 +73,7 @@ def run_all(): run_example("equation_flow_elmer_2D", run_solver=True) run_example("equation_flux_elmer", run_solver=True) run_example("equation_magnetodynamics_elmer", run_solver=True) + run_example("equation_magnetodynamics_2D_elmer.py", run_solver=True) run_example("frequency_beamsimple", run_solver=True) run_example("material_multiple_bendingbeam_fiveboxes", run_solver=True) run_example("material_multiple_bendingbeam_fivefaces", run_solver=True) @@ -108,6 +109,7 @@ def setup_all(): run_example("equation_flow_elmer_2D") run_example("equation_flux_elmer") run_example("equation_magnetodynamics_elmer") + run_example("equation_magnetodynamics_2D_elmer.py") run_example("frequency_beamsimple") run_example("material_multiple_bendingbeam_fiveboxes") run_example("material_multiple_bendingbeam_fivefaces") From 3a2a30810197924ea56a156f8508bd348d0ffe07 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Thu, 23 Feb 2023 10:55:41 +0100 Subject: [PATCH 5/8] Arch: IFC, import, add some comment --- src/Mod/Arch/importIFCHelper.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Mod/Arch/importIFCHelper.py b/src/Mod/Arch/importIFCHelper.py index 0ebb2490bf..2ccb49a695 100644 --- a/src/Mod/Arch/importIFCHelper.py +++ b/src/Mod/Arch/importIFCHelper.py @@ -385,6 +385,10 @@ def buildRelProductColors(ifcfile, prodrepr): i = 0 for p in prodrepr.keys(): + # see method getColorFromProduct() + # it is a method for the redundant code inside this loop + # which can be used to get the color from a product directly + # Representation item, see `IfcRepresentationItem` documentation. # All kinds of geometric or topological representation items # `IfcExtrudedAreaSolid`, `IfcMappedItem`, `IfcFacetedBrep`, From 2d5aad845ac244fd79c51e7597dd06e34f494950 Mon Sep 17 00:00:00 2001 From: Uwe Date: Thu, 23 Feb 2023 04:16:52 +0100 Subject: [PATCH 6/8] [FEM] simplify Magnetodynamics 2D example - thanks to the forum: https://forum.freecad.org/viewtopic.php?p=662830#p662830 --- .../equation_magnetodynamics_2D_elmer.py | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/Mod/Fem/femexamples/equation_magnetodynamics_2D_elmer.py b/src/Mod/Fem/femexamples/equation_magnetodynamics_2D_elmer.py index c406601e12..0cfe04f4da 100644 --- a/src/Mod/Fem/femexamples/equation_magnetodynamics_2D_elmer.py +++ b/src/Mod/Fem/femexamples/equation_magnetodynamics_2D_elmer.py @@ -29,6 +29,7 @@ from FreeCAD import Vector import Draft import ObjectsFem +import Part from BOPTools import SplitFeatures from . import manager @@ -113,24 +114,12 @@ def setup(doc=None, solvertype="elmer"): Powder.Label = "Powder" Powder.ViewObject.Visibility = False - # circle defining later the air volume - Air_Circle = Draft.make_circle(134.536) - Air_Circle.Placement = FreeCAD.Placement( - FreeCAD.Vector(0.0, 60.0, 0.0), - FreeCAD.Rotation(0, 0, 0), - FreeCAD.Vector(0, 0, 1), - ) - Air_Circle.Label = "Air_Circle" - Air_Circle.ViewObject.Visibility = False - - # wire to cut the air circle - p1 = Vector(0.0, -100.0, 0.0) - p2 = Vector(-140.0, -100.0, 0.0) - p3 = Vector(-140.0, 200.0, 0.0) - p4 = Vector(0.0, 200.0, 0.0) - Air_Rectangle = Draft.make_wire([p1, p2, p3, p4], closed=True) - Air_Rectangle.Label = "Air_Rectangle" - Air_Rectangle.ViewObject.Visibility = False + # a half circle defining later the air volume + Air_Circle =Part.makeCircle( + 140.0, Vector(0.0, 60.0, 0.0), Vector(0.0, 0.0, 1.0), -90.0, 90.0) + Air_Line = Part.makeLine((0.0, -80.0, 0.0), (0.0, 200.0, 0.0)) + Air_Area = doc.addObject("Part::Feature", "Air_Area") + Air_Area.Shape = Part.Face([Part.Wire([Air_Circle, Air_Line])]) # a link of the Insulation InsulationLink = doc.addObject("App::Link", "Link_Insulation") @@ -158,12 +147,12 @@ def setup(doc=None, solvertype="elmer"): # fusion of all links Fusion = doc.addObject("Part::MultiFuse", "Fusion") - Fusion.Shapes = [Air_Rectangle, InsulationLink, CoilLink, CrucibleLink, PowderLink] + Fusion.Shapes = [InsulationLink, CoilLink, CrucibleLink, PowderLink] Fusion.ViewObject.Visibility = False # cut all objects from Air wire to get volume of fluid Cut = doc.addObject("Part::Cut", "Cut_Air") - Cut.Base = Air_Circle + Cut.Base = Air_Area Cut.Tool = Fusion Cut.ViewObject.Visibility = False From c8305549e2087e54f1ddc96edb135afeff4e3c2c Mon Sep 17 00:00:00 2001 From: Uwe Date: Thu, 23 Feb 2023 12:39:50 +0100 Subject: [PATCH 7/8] [FEM] [skip ci] fix typos - spot by the CI --- src/Mod/Fem/femtaskpanels/task_material_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/femtaskpanels/task_material_common.py b/src/Mod/Fem/femtaskpanels/task_material_common.py index c4d9603087..562b0a4b21 100644 --- a/src/Mod/Fem/femtaskpanels/task_material_common.py +++ b/src/Mod/Fem/femtaskpanels/task_material_common.py @@ -274,7 +274,7 @@ class _TaskPanel: # if all items were found we know it is the right card # we can hereby not simply perform # set(self.materials[a_mat].items()) ^ set(material.items()) - # because emtries are often identic, just appear in the set in a different order + # because entries are often identical, just appear in the set in a different order unmatched_item = False for item in material.items(): if item not in self.materials[a_mat].items(): From 5fa3e021e894cc835217607577d2e86f3acb302e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20B=C3=A4hr?= Date: Mon, 13 Feb 2023 20:16:04 +0100 Subject: [PATCH 8/8] PD: Fix the internal InvoluteGear's root/tip arc directions The arcs on the tip (around the addendum) and the root (between the fillets) used to have their bulge in the wrong direction for the internal gear profile: their center points has been on the outside. Since this error existed since the very first introcution of the internal gear I assume this was done by accident: Instead of only the fillets, all arcs have been inverted when the external profile was copied. This commit now inverts the tip and root arcs again, to be concentric with the pitch circle -- same as for the external profile. --- src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py | 2 +- src/Mod/PartDesign/fcgear/involute.py | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py b/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py index 2dfdbb6d71..a732c5e449 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py +++ b/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py @@ -125,7 +125,7 @@ class TestInvoluteGear(unittest.TestCase): root_diameter = pitch_diameter + 2 * ded_coef * m # the test purpose here is just to ensure the gear's parameters are used, # not super precise profile verification. Thus a lax delta is just file here. - delta = 0.1 # FIXME it seems that the top land arc is in the wrong direction, thus a larger tolerance. + delta = 0.01 self.assertIntersection(hub.Shape, makeCircle(pitch_diameter/2), "Expecting intersection at pitch circle") self.assertNoIntersection(hub.Shape, makeCircle(tip_diameter/2 - delta), "Teeth extent below tip circle") self.assertNoIntersection(hub.Shape, makeCircle(root_diameter/2 + delta), "Teeth extend beyond root circle") diff --git a/src/Mod/PartDesign/fcgear/involute.py b/src/Mod/PartDesign/fcgear/involute.py index d749f4b930..4bbafde3d5 100644 --- a/src/Mod/PartDesign/fcgear/involute.py +++ b/src/Mod/PartDesign/fcgear/involute.py @@ -312,11 +312,7 @@ def CreateInternalGear(w, m, Z, phi, if (not tipWithinInvolute): w.line(tip) # line from tip down to base circle - if split: - w.arc(tipR, Ra, 0) # arc across addendum circle - else: - #w.arc(tipR[-1], Ra, 0) # arc across addendum circle - w.arc(tipR, Ra, 0) + w.arc(tipR, Ra, 1) # arc across addendum circle if (not tipWithinInvolute): w.line(invR[0]) # line up to the base circle @@ -329,7 +325,7 @@ def CreateInternalGear(w, m, Z, phi, if (rootNext[1] > rootR[1]): # is there a section of root circle between fillets? w.arc(rootR, fRad, 1) # back fillet - w.arc(rootNext, Rroot, 0) # root circle arc + w.arc(rootNext, Rroot, 1) # root circle arc w.arc(filletNext, fRad, 1)