Merge branch 'FreeCAD:master' into master

This commit is contained in:
mosfet80
2023-02-23 13:04:24 +01:00
committed by GitHub
12 changed files with 397 additions and 41 deletions

View File

@@ -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`,

View File

@@ -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

View File

@@ -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);

View File

@@ -0,0 +1,279 @@
# ***************************************************************************
# * Copyright (c) 2023 Uwe Stöhr <uwestoehr@lyx.org> *
# * *
# * 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
import Part
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
# 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")
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 = [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_Area
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

View File

@@ -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")

View File

@@ -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":

View File

@@ -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 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():
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 ""

View File

@@ -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

View File

@@ -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)

View File

@@ -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")

View File

@@ -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)

View File

@@ -32,4 +32,3 @@ FreeCADGui.loadFile(filename, mod)
from StartPage import StartPage
StartPage.postStart()