Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/icepak fluent #650

Merged
merged 14 commits into from
Dec 15, 2021
12 changes: 11 additions & 1 deletion _unittest/test_98_Icepak.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pyaedt.generic.filesystem import Scratch

# Setup paths for module imports
from _unittest.conftest import local_path, scratch_path, desktop_version
from _unittest.conftest import local_path, scratch_path, desktop_version, config

try:
import pytest # noqa: F401
Expand All @@ -32,6 +32,7 @@
src_project_name = "USB_Connector_IPK"
source_project = os.path.join(local_path, "example_models", src_project_name + ".aedt")
source_project_path = os.path.join(local_path, "example_models", src_project_name)
source_fluent = os.path.join(local_path, "example_models", "ColdPlateExample.aedt")


class TestClass:
Expand All @@ -45,6 +46,7 @@ def setup_class(self):

self.test_project = self.local_scratch.copyfile(example_project)
self.test_src_project = self.local_scratch.copyfile(source_project)

self.local_scratch.copyfolder(
os.path.join(local_path, "example_models", test_project_name + ".aedb"),
os.path.join(self.local_scratch.path, test_project_name + ".aedb"),
Expand Down Expand Up @@ -380,3 +382,11 @@ def test_89_check_bounding_box(self):
exp_bounding = [0.2, 0.2, 0.2, 0.5, 0.6, 0.4]
real_bound = obj_2_bbox
assert abs(sum([i - j for i, j in zip(exp_bounding, real_bound)])) < tol

@pytest.mark.skipif(config["build_machine"], reason="Needs Workbench to run.")
def test_90_export_fluent_mesh(self):
self.fluent = self.local_scratch.copyfile(source_fluent)
app = Icepak(self.fluent)
assert app.get_liquid_objects() == ["Liquid"]
assert app.get_gas_objects() == ["Region"]
assert app.generate_fluent_mesh()
Binary file added doc/source/Resources/spiral.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 9 additions & 4 deletions pyaedt/application/AnalysisIcepak.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import csv
import re
import os

from pyaedt.generic.general_methods import aedt_exception_handler, generate_unique_name, is_ironpython
from pyaedt.application.Analysis import Analysis
Expand Down Expand Up @@ -253,7 +254,11 @@ def export_3d_model(self, fileName, filePath, fileFormat=".step", object_list=[]
allObjects = object_list[:]

self.logger.info("Exporting {} objects".format(len(allObjects)))

major = -1
minor = -1
if fileFormat in [".step", ".stp", ".sm3", ".sat", ".sab"]:
major = 29
minor = 0
Comment on lines +261 to +262
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxcapodi78 Could you add a short comment here to explain those values? Maybe it is something common and usual for Icepak user.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

stringa = ",".join(allObjects)
arg = [
"NAME:ExportParameters",
Expand All @@ -264,11 +269,11 @@ def export_3d_model(self, fileName, filePath, fileFormat=".step", object_list=[]
"Selections:=",
stringa,
"File Name:=",
str(filePath) + "/" + str(fileName) + str(fileFormat),
os.path.join(filePath, fileName + fileFormat),
"Major Version:=",
-1,
major,
"Minor Version:=",
-1,
minor,
]

self.modeler.oeditor.Export(arg)
Expand Down
148 changes: 148 additions & 0 deletions pyaedt/icepak.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@
import math
import os
import re
import warnings
from collections import OrderedDict

if os.name == "posix":
try:
import subprocessdotnet as subprocess
except:
warnings.warn("Pythonnet is needed to run pyaedt within Linux")
else:
import subprocess

from pyaedt.application.AnalysisIcepak import FieldAnalysisIcepak
from pyaedt.generic.general_methods import generate_unique_name, aedt_exception_handler
from pyaedt.generic.DataHandlers import _arg2dict
Expand Down Expand Up @@ -2331,3 +2340,142 @@ def delete_pcb_component(self, comp_name):

self.modeler.oeditor.Delete(arg)
return True

@aedt_exception_handler
def get_liquid_objects(self):
"""Return the fluids materials objects.
maxcapodi78 marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
list
List of objects names
"""
mats = []
for el in self.materials.liquids:
mats.extend(self.modeler.convert_to_selections(self.modeler.get_objects_by_material(el), True))
return mats

@aedt_exception_handler
def get_gas_objects(self):
"""Return the gas materials objects.

Returns
-------
list
List of all Gas objects.
"""
mats = []
for el in self.materials.gases:
mats.extend(self.modeler.convert_to_selections(self.modeler.get_objects_by_material(el), True))
return mats

@aedt_exception_handler
def generate_fluent_mesh(self, object_lists=None):
"""Generate a Fluent Mesh for selected object list and assign automatically to the objects.
maxcapodi78 marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
object_lists : list, optional
Lis of objects on which compute Fluent Mesh. If `None` mesh will be done on fluids objects.

Returns
-------
:class:`pyaedt.modules.Mesh.MeshOperation`
"""
version = self.aedt_version_id[-3:]
ansys_install_dir = os.environ.get("ANSYS{}_DIR".format(version), "")
if not ansys_install_dir:
ansys_install_dir = os.environ.get("AWP_ROOT{}".format(version), "")
assert ansys_install_dir, "Fluent {} has to be installed on to generate mesh.".format(version)
assert os.getenv("ANSYS{}_DIR".format(version))
if not object_lists:
object_lists = self.get_liquid_objects()
assert object_lists, "No Fluids objects found."
object_lists = self.modeler.convert_to_selections(object_lists, True)
file_name = self.project_name
sab_file_pointer = os.path.join(self.project_path, file_name + ".sab")
mesh_file_pointer = os.path.join(self.project_path, file_name + ".msh")
fl_uscript_file_pointer = os.path.join(self.project_path, "FLUscript.jou")
if os.path.exists(mesh_file_pointer):
os.remove(mesh_file_pointer)
if os.path.exists(sab_file_pointer):
os.remove(sab_file_pointer)
if os.path.exists(fl_uscript_file_pointer):
os.remove(fl_uscript_file_pointer)
if os.path.exists(mesh_file_pointer + ".trn"):
os.remove(mesh_file_pointer + ".trn")
assert self.export_3d_model(file_name, self.project_path, ".sab", object_lists), "Failed to export .sab"

# Building Fluent journal script file *.jou
fluent_script = open(fl_uscript_file_pointer, "w")
fluent_script.write("/file/start-transcript " + '"' + mesh_file_pointer + '.trn"\n')
fluent_script.write(
'/file/set-tui-version "{}"\n'.format(self.aedt_version_id[-3:-1] + "." + self.aedt_version_id[-1:])
)
fluent_script.write("(enable-feature 'serial-hexcore-without-poly)\n")
fluent_script.write('(cx-gui-do cx-activate-tab-index "NavigationPane*Frame1(TreeTab)" 0)\n')
fluent_script.write("(%py-exec \"workflow.InitializeWorkflow(WorkflowType=r'Watertight Geometry')\")\n")
cmd = "(%py-exec \"workflow.TaskObject['Import Geometry']."
cmd += "Arguments.setState({r'FileName': r'" + sab_file_pointer + "',})\")\n"
fluent_script.write(cmd)
fluent_script.write("(%py-exec \"workflow.TaskObject['Import Geometry'].Execute()\")\n")
fluent_script.write("(%py-exec \"workflow.TaskObject['Add Local Sizing'].AddChildToTask()\")\n")
fluent_script.write("(%py-exec \"workflow.TaskObject['Add Local Sizing'].Execute()\")\n")
fluent_script.write("(%py-exec \"workflow.TaskObject['Generate the Surface Mesh'].Execute()\")\n")
cmd = "(%py-exec \"workflow.TaskObject['Describe Geometry'].UpdateChildTasks(SetupTypeChanged=False)\")\n"
fluent_script.write(cmd)
cmd = "(%py-exec \"workflow.TaskObject['Describe Geometry']."
cmd += "Arguments.setState({r'SetupType': r'The geometry consists of only fluid regions with no voids',})\")\n"
fluent_script.write(cmd)
cmd = "(%py-exec \"workflow.TaskObject['Describe Geometry'].UpdateChildTasks(SetupTypeChanged=True)\")\n"
fluent_script.write(cmd)
cmd = "(%py-exec \"workflow.TaskObject['Describe Geometry'].Arguments.setState({r'InvokeShareTopology': r'Yes',"
cmd += "r'SetupType': r'The geometry consists of only fluid regions with no voids',r'WallToInternal': "
cmd += "r'Yes',})\")\n"
fluent_script.write(cmd)
cmd = "(%py-exec \"workflow.TaskObject['Describe Geometry'].UpdateChildTasks(SetupTypeChanged=False)\")\n"
fluent_script.write(cmd)
fluent_script.write("(%py-exec \"workflow.TaskObject['Describe Geometry'].Execute()\")\n")
fluent_script.write("(%py-exec \"workflow.TaskObject['Apply Share Topology'].Execute()\")\n")
fluent_script.write("(%py-exec \"workflow.TaskObject['Update Boundaries'].Execute()\")\n")
fluent_script.write("(%py-exec \"workflow.TaskObject['Update Regions'].Execute()\")\n")
fluent_script.write("(%py-exec \"workflow.TaskObject['Add Boundary Layers'].AddChildToTask()\")\n")
fluent_script.write("(%py-exec \"workflow.TaskObject['Add Boundary Layers'].InsertCompoundChildTask()\")\n")
cmd = "(%py-exec \"workflow.TaskObject['smooth-transition_1']."
cmd += "Arguments.setState({r'BLControlName': r'smooth-transition_1',})\")\n"
fluent_script.write(cmd)
fluent_script.write("(%py-exec \"workflow.TaskObject['Add Boundary Layers'].Arguments.setState({})\")\n")
fluent_script.write("(%py-exec \"workflow.TaskObject['smooth-transition_1'].Execute()\")\n")
# r'VolumeFill': r'hexcore' / r'tetrahedral'
cmd = "(%py-exec \"workflow.TaskObject['Generate the Volume Mesh'].Arguments.setState({r'VolumeFill': "
cmd += "r'hexcore', r'VolumeMeshPreferences': {r'MergeBodyLabels': r'yes',},})\")\n"
fluent_script.write(cmd)
fluent_script.write("(%py-exec \"workflow.TaskObject['Generate the Volume Mesh'].Execute()\")\n")
fluent_script.write("/file/hdf no\n")
fluent_script.write('/file/write-mesh "' + mesh_file_pointer + '"\n')
fluent_script.write("/file/stop-transcript\n")
fluent_script.write("/exit,\n")
fluent_script.close()

# Fluent command line parameters: -meshing -i <journal> -hidden -tm<x> (# processors for meshing) -wait
fl_ucommand = [
os.path.join(self.desktop_install_dir, "fluent", "ntbin", "win64", "fluent.exe"),
"3d",
"-meshing",
"-hidden",
"-i" + '"' + fl_uscript_file_pointer + '"',
]
self.logger.info("Fluent will be started in BG!")
subprocess.call(fl_ucommand)
if os.path.exists(mesh_file_pointer + ".trn"):
os.remove(mesh_file_pointer + ".trn")
if os.path.exists(fl_uscript_file_pointer):
os.remove(fl_uscript_file_pointer)
if os.path.exists(sab_file_pointer):
os.remove(sab_file_pointer)
if os.path.exists(mesh_file_pointer):
self.logger.info("'" + mesh_file_pointer + "' has been created.")
return self.mesh.assign_mesh_from_file(object_lists, mesh_file_pointer)
self.logger.error("Failed to create msh file")

return False
30 changes: 30 additions & 0 deletions pyaedt/modules/MaterialLib.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,36 @@ def __getitem__(self, item):
elif item in list(self.surface_material_keys.keys()):
return self.surface_material_keys[item]

@property
def liquids(self):
"""Return the liquids materials. A liquid is considered a fluid with density greater than 100Kg/m3.
maxcapodi78 marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
list
List of fluid materials.
"""
mats = []
for el, val in self.material_keys.items():
if val.thermal_material_type == "Fluid" and val.mass_density.value and float(val.mass_density.value) >= 100:
mats.append(el)
return mats

@property
def gases(self):
"""Return the gas materials. A gas is considered a fluid with density lower than 100Kg/m3.
maxcapodi78 marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
list
List of all Gas materials.
"""
mats = []
for el, val in self.material_keys.items():
if val.thermal_material_type == "Fluid" and val.mass_density.value and float(val.mass_density.value) < 100:
mats.append(el)
return mats

@aedt_exception_handler
def _get_materials(self):
"""Get materials."""
Expand Down
5 changes: 3 additions & 2 deletions pyaedt/modules/Mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ def create(self):

Returns
-------
type
bool
``True`` when successful, ``False`` when failed.

"""
if self.type == "SurfApproxBased":
Expand All @@ -78,9 +79,9 @@ def create(self):
self._meshicepak.omeshmodule.AssignMeshOperation(self._get_args())
elif self.type == "CurvatureExtraction":
self._meshicepak.omeshmodule.AssignCurvatureExtractionOp(self._get_args())

else:
return False
return True

@aedt_exception_handler
def update(self):
Expand Down
48 changes: 47 additions & 1 deletion pyaedt/modules/MeshIcepak.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def assign_mesh_level(self, mesh_order, meshop_name=None):

Returns
-------
bool
list of :class:`pyaedt.modules.Mesh.MeshOperation`
``True`` when successful, ``False`` when failed.

References
Expand All @@ -306,6 +306,52 @@ def assign_mesh_level(self, mesh_order, meshop_name=None):
list_meshops.append(meshop_name)
return list_meshops

@aedt_exception_handler
def assign_mesh_from_file(self, objects, filename, meshop_name=None):
"""Assign a mesh from file to objects.

Parameters
----------
objects : list
List of objects to which apply the mesh file.
filename : str
Full path to .msh file.
filename : str
Full path to .msh file.
MaxJPRey marked this conversation as resolved.
Show resolved Hide resolved
meshop_name : str, optional
Name of the mesh operations
maxcapodi78 marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
:class:`pyaedt.modules.Mesh.MeshOperation`
Mesh Operation object. ``False`` when failed.

References
----------

>>> oModule.AssignMeshOperation
"""
objs = self._app.modeler.convert_to_selections(objects, True)
if meshop_name:
meshop_name = generate_unique_name("MeshFile")
else:
meshop_name = generate_unique_name("MeshFile")
props = OrderedDict({"Enable": True, "MaxLevel": str(0), "MinLevel": str(0), "Objects": objs})
props["Local Mesh Parameters Enabled"] = False
props["Mesh Reuse Enabled"] = True
props["Mesh Reuse File"] = filename
props["Local Mesh Parameters Type"] = "3DPolygon Local Mesh Parameters"
props["Height count"] = "0"
props["Top height"] = "0mm"
props["Top ratio"] = "0"
props["Bottom height"] = "0mm"
props["Bottom ratio"] = "0"
mop = MeshOperation(self, meshop_name, props, "Icepak")
if mop.create():
self.meshoperations.append(mop)
return mop
return False

@aedt_exception_handler
def automatic_mesh_pcb(self, accuracy=2):
"""Create a custom mesh tailored on a PCB design.
Expand Down
7 changes: 0 additions & 7 deletions pyaedt/third_party/ironpython/plumbum/fs/atomic.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,9 @@ class AtomicFile(object):
"""
Atomic file operations implemented using file-system advisory locks (``flock`` on POSIX,
``LockFile`` on Windows).

.. note::
On Linux, the manpage says ``flock`` might have issues with NFS mounts. You should
take this into account.

.. versionadded:: 1.3
"""

Expand Down Expand Up @@ -126,7 +124,6 @@ def locked(self, blocking=True):
"""
A context manager that locks the file; this function is reentrant by the thread currently
holding the lock.

:param blocking: if ``True``, the call will block until we can grab the file system lock.
if ``False``, the call may fail immediately with the underlying exception
(``IOError`` or ``WindowsError``)
Expand Down Expand Up @@ -188,14 +185,11 @@ class AtomicCounterFile(object):
"""
An atomic counter based on AtomicFile. Each time you call ``next()``, it will
atomically read and increment the counter's value, returning its previous value

Example::

acf = AtomicCounterFile.open("/some/file")
print acf.next() # e.g., 7
print acf.next() # 8
print acf.next() # 9

.. versionadded:: 1.3
"""

Expand Down Expand Up @@ -266,7 +260,6 @@ class PidFile(object):
(the OS will clear the lock when the process exits). It is used to prevent two instances
of the same process (normally a daemon) from running concurrently. The PID file holds its
process' PID, so you know who's holding it.

.. versionadded:: 1.3
"""

Expand Down