diff --git a/.github/workflows/ci_with_benchmarks.yml b/.github/workflows/ci_with_benchmarks.yml index b82c303..d097c6b 100644 --- a/.github/workflows/ci_with_benchmarks.yml +++ b/.github/workflows/ci_with_benchmarks.yml @@ -18,7 +18,7 @@ on: jobs: testing: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout repository uses: actions/checkout@v4 @@ -37,9 +37,9 @@ jobs: sudo apt-get upgrade -y sudo apt-get install -y libgl1-mesa-glx libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev libosmesa6 libosmesa6-dev libgles2-mesa-dev libarchive-dev libpangocairo-1.0-0 mamba activate - mamba install -y -c conda-forge "openmc=0.14.0=dagmc*nompi*" moab>=5.3.0 gmsh python-gmsh - python -m pip install --upgrade pip - python -m pip install . + mamba install -y -c conda-forge "openmc=0.15.0=dagmc*nompi*" trimesh networkx cadquery gmsh python-gmsh + python -m ensurepip --upgrade + python -m pip install . --no-deps python -m pip install openmc_data_downloader openmc_data_downloader -l ENDFB-7.1-NNDC -i Fe56 Be9 git clone --single-branch -b main --depth 1 https://github.com/fusion-energy/model_benchmark_zoo.git diff --git a/.github/workflows/ci_with_install.yml b/.github/workflows/ci_with_conda_install.yml similarity index 86% rename from .github/workflows/ci_with_install.yml rename to .github/workflows/ci_with_conda_install.yml index 8ba07fa..17ab34a 100644 --- a/.github/workflows/ci_with_install.yml +++ b/.github/workflows/ci_with_conda_install.yml @@ -1,7 +1,7 @@ # This CI does includes particle transport tests as openmc is installed -name: CI with install +name: CI with Conda install on: pull_request: @@ -20,7 +20,7 @@ on: jobs: testing: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: python-version: ["3.10", "3.11", "3.12"] @@ -42,11 +42,12 @@ jobs: mamba activate mamba create -y --name cad_to_dagmc python=${{ matrix.python-version }} mamba activate cad_to_dagmc - mamba install -y -c conda-forge "openmc=0.14.0=dagmc*nompi*" gmsh python-gmsh - python -m pip install --upgrade pip - python -m pip install . + mamba install -y -c conda-forge "openmc=0.15.0=dagmc*nompi*" trimesh networkx cadquery gmsh python-gmsh + python -m ensurepip --upgrade + python -m pip install . --no-deps python -c "import cad_to_dagmc" - python -m pip install .[tests] + mamba install -y -c conda-forge pytest vtk + python -m pip install .[tests] --no-deps pytest -v tests python examples/surface_mesh/cadquery_assembly.py python examples/surface_mesh/cadquery_compound.py diff --git a/.github/workflows/ci_with_pip_install.yml b/.github/workflows/ci_with_pip_install.yml index ba69623..af683a1 100644 --- a/.github/workflows/ci_with_pip_install.yml +++ b/.github/workflows/ci_with_pip_install.yml @@ -20,7 +20,7 @@ on: jobs: testing: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: image: openmc/openmc:develop-dagmc steps: @@ -33,26 +33,19 @@ jobs: apt-get --allow-releaseinfo-change update apt-get update -y apt-get upgrade -y - apt-get install -y libgl1-mesa-glx libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev libosmesa6 libosmesa6-dev libgles2-mesa-dev libarchive-dev libpangocairo-1.0-0 libxcursor-dev libxft2 libxinerama-dev - apt-get install -y make cmake + apt-get install -y libhdf5-dev libblas-dev liblapack-dev libgl1-mesa-glx libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev libosmesa6 libosmesa6-dev libgles2-mesa-dev libarchive-dev libpangocairo-1.0-0 libxcursor-dev libxft2 libxinerama-dev make cmake libeigen3-dev apt install python3 apt install python3-pip python -m pip install --upgrade pip - mkdir MOAB - cd MOAB - git clone --single-branch -b 5.5.1 --depth 1 https://bitbucket.org/fathomteam/moab/ - mkdir build - cd build - apt-get install -y libeigen3-dev - cmake ../moab -DENABLE_PYMOAB=ON -DENABLE_HDF5=ON -DENABLE_BLASLAPACK=OFF -DENABLE_FORTRAN=OFF - make -j2 - make install - cd ../.. - python -m pip install . - python -c "import cad_to_dagmc" + git clone --single-branch -b master --depth 1 https://bitbucket.org/fathomteam/moab/ + cd moab + git checkout faf7e989c0fd0be4a19f61c1d4713afc5397c8a7 + python -m pip install . --config-settings=cmake.args=-DENABLE_HDF5=ON + cd .. + python -c "import pymoab" python -m pip install .[tests] + python -c "import cad_to_dagmc" pytest -v tests - cd examples python examples/surface_mesh/cadquery_assembly.py python examples/surface_mesh/cadquery_compound.py python examples/surface_mesh/cadquery_object_and_stp_file.py diff --git a/.gitignore b/.gitignore index d60417f..ffc1a2a 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,4 @@ dmypy.json *.stp src/_version.py *.msh +*.step diff --git a/README.md b/README.md index 2c0baae..39a864b 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,6 @@ Also checkout these other software projects that also create DAGMC geometry [CAD - Install using Mamba - Install using Conda -- Install using Mamba and pip -- Install using Conda and pip - Install using pip and source compilations ## Install using Mamba @@ -84,73 +82,32 @@ Then you can install the cad_to_dagmc package conda install -y -c conda-forge cad_to_dagmc ``` -## Install using Mamba and pip +## Install using pip and source compilations -In principle, installing any Conda/Mamba distribution will work. A few Conda/Mamba options are: -- [Miniforge](https://github.com/conda-forge/miniforge) (recommended as it includes mamba) -- [Anaconda](https://www.anaconda.com/download) -- [Miniconda](https://docs.conda.io/en/latest/miniconda.html) +It is also possible to avoid the use of conda/mamba and installing using pip. -This example assumes you have installed the Miniforge option or separately have installed Mamba with ```conda install -c conda-forge mamba -y``` +First ensure hdf5 is installed as this is needed by MOAB pip install command -Create a new environment, I've chosen Python 3.10 here but newer versions are -also supported. -```bash -mamba create --name new_env python=3.10 -y ``` - -Activate the environment -```bash -mamba activate new_env +sudo apt-get install libhdf5-dev ``` -Install the dependencies -```bash -mamba install -y -c conda-forge "moab>=5.3.0" gmsh python-gmsh -``` +Then clone the latest version of MOAB and cd into the moab directory. -Then you can install the cad_to_dagmc package -```bash -pip install cad_to_dagmc ``` - - -## Install using Conda and pip - -In principle, installing any Conda/Mamba distribution will work. A few Conda/Mamba options are: -- [Miniforge](https://github.com/conda-forge/miniforge) (recommended as it includes mamba) -- [Anaconda](https://www.anaconda.com/download) -- [Miniconda](https://docs.conda.io/en/latest/miniconda.html) - -This example uses Conda to install some dependencies that are not available via PyPi. - -Create a new environment -```bash -conda create --name new_env python=3.10 -y +git clone master https://bitbucket.org/fathomteam/moab/ +cd moab ``` -Activate the environment -```bash -conda activate new_env +Ensure pip is up to date as a new version is needed ``` - -Install the dependencies -```bash -conda install -y -c conda-forge "moab>=5.3.0" gmsh python-gmsh +python -m pip install --upgrade pip ``` -Then you can install the cad_to_dagmc package -```bash -pip install cad_to_dagmc +Run the pip install command with cmake arguments. +``` +pip install . --config-settings=cmake.args=-DENABLE_HDF5=ON ``` - -## Install using pip and source compilations - -It should possible to avoid the use of conda and installing using pip and compiling from source. - -First compile MOAB (and install Pymoab) from source - -Then install gmsh from source (installing from pip appears to cause conflicts with the open cascade used in ocp and cadquery) Then you can install the cad_to_dagmc package with ```pip``` @@ -178,7 +135,7 @@ The package requires newer versions of Linux. For example the package does not w The package requires newer versions of pip. It is recommended to ensure that your version of pip is up to date. This can be done with ```python -m pip install --upgrade pip``` -Installing one of the package dependancies (gmsh) with pip appears to result in occational errors when passing cad objects between cadquery / ocp and gmsh. The conda install gmsh appears to work fine. +Installing one of the package dependancies (gmsh) with pip appears to result in errors when passing cad objects in memory between cadquery / ocp and gmsh. The default method of passing cad objects is via file so this should not impact most users. The conda install gmsh appears to work fine with in memory passing of cad objects as the version of OCP matches between Gmsh and CadQuery. # Usage - creation of DAGMC h5m files diff --git a/examples/unstrucutred_volume_mesh/curved_cadquery_object_to_dagmc_volume_mesh.py b/examples/unstrucutred_volume_mesh/curved_cadquery_object_to_dagmc_volume_mesh.py index a6b7d6b..d5bbfe5 100644 --- a/examples/unstrucutred_volume_mesh/curved_cadquery_object_to_dagmc_volume_mesh.py +++ b/examples/unstrucutred_volume_mesh/curved_cadquery_object_to_dagmc_volume_mesh.py @@ -56,7 +56,7 @@ def gear(t, r1=4, r2=1): my_model = CadToDagmc() -my_model.add_cadquery_object(result) -my_model.add_cadquery_object(result2) +my_model.add_cadquery_object(result, material_tags=["mat1"]) +my_model.add_cadquery_object(result2, material_tags=["mat2"]) my_model.export_unstructured_mesh_file(filename="umesh.h5m", max_mesh_size=1, min_mesh_size=0.1) diff --git a/pyproject.toml b/pyproject.toml index 7c8c453..c9c253f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,8 @@ dependencies = [ "trimesh", "networkx", "cadquery>=2.4.0", + "numpy<=1.23.5", + "gmsh" ] dynamic = ["version"] diff --git a/src/cad_to_dagmc/core.py b/src/cad_to_dagmc/core.py index d0408bd..45966fd 100644 --- a/src/cad_to_dagmc/core.py +++ b/src/cad_to_dagmc/core.py @@ -1,13 +1,12 @@ -import typing - import cadquery as cq import gmsh import numpy as np -from cadquery import importers +from cadquery import importers, exporters from pymoab import core, types +import tempfile -def _define_moab_core_and_tags() -> typing.Tuple[core.Core, dict]: +def _define_moab_core_and_tags() -> tuple[core.Core, dict]: """Creates a MOAB Core instance which can be built up by adding sets of triangles to the instance @@ -62,14 +61,11 @@ def _define_moab_core_and_tags() -> typing.Tuple[core.Core, dict]: def _vertices_to_h5m( - vertices: typing.Union[ - typing.Iterable[typing.Tuple[float, float, float]], - typing.Iterable["cadquery.occ_impl.geom.Vector"], - ], - triangles_by_solid_by_face: typing.Iterable[typing.Iterable[typing.Tuple[int, int, int]]], - material_tags: typing.Iterable[str], - h5m_filename="dagmc.h5m", - implicit_complement_material_tag=None, + vertices: list[tuple[float, float, float]] | list["cadquery.occ_impl.geom.Vector"], + triangles_by_solid_by_face: list[list[tuple[int, int, int]]], + material_tags: list[str], + h5m_filename: str = "dagmc.h5m", + implicit_complement_material_tag: str | None = None, ): """Converts vertices and triangle sets into a tagged h5m file compatible with DAGMC enabled neutronics simulations @@ -194,8 +190,29 @@ def _vertices_to_h5m( return h5m_filename +def get_volumes(gmsh, assembly, method="file"): + + if method == "in memory": + volumes = gmsh.model.occ.importShapesNativePointer(assembly.wrapped._address()) + gmsh.model.occ.synchronize() + elif method == "file": + with tempfile.NamedTemporaryFile(suffix=".step") as temp_file: + exporters.export(assembly, temp_file.name) + volumes = gmsh.model.occ.importShapes(temp_file.name) + gmsh.model.occ.synchronize() + + return gmsh, volumes + + +def init_gmsh(): + gmsh.initialize() + gmsh.option.setNumber("General.Terminal", 1) + gmsh.model.add("made_with_cad_to_dagmc_package") + return gmsh + + def _mesh_brep( - occ_shape: str, + gmsh, min_mesh_size: float = 1, max_mesh_size: float = 10, mesh_algorithm: int = 1, @@ -219,18 +236,12 @@ def _mesh_brep( The resulting gmsh object and volumes """ - gmsh.initialize() - gmsh.option.setNumber("General.Terminal", 1) - gmsh.model.add("made_with_cad_to_dagmc_package") - volumes = gmsh.model.occ.importShapesNativePointer(occ_shape) - gmsh.model.occ.synchronize() - gmsh.option.setNumber("Mesh.Algorithm", mesh_algorithm) gmsh.option.setNumber("Mesh.MeshSizeMin", min_mesh_size) gmsh.option.setNumber("Mesh.MeshSizeMax", max_mesh_size) gmsh.model.mesh.generate(dimensions) - return gmsh, volumes + return gmsh def mesh_to_vertices_and_triangles( @@ -336,18 +347,18 @@ def __init__(self, filename: str): def export_dagmc_h5m_file( self, - material_tags: typing.Iterable[str], - implicit_complement_material_tag: typing.Optional[str] = None, + material_tags: list[str], + implicit_complement_material_tag: str | None = None, filename: str = "dagmc.h5m", ): """Saves a DAGMC h5m file of the geometry Args: - material_tags (typing.Iterable[str]): the names of the DAGMC + material_tags (list[str]): the names of the DAGMC material tags to assign. These will need to be in the same order as the volumes in the GMESH mesh and match the material tags used in the neutronics code (e.g. OpenMC). - implicit_complement_material_tag (typing.Optional[str], optional): + implicit_complement_material_tag (str | None, optional): the name of the material tag to use for the implicit complement (void space). Defaults to None which is a vacuum. filename (str, optional): _description_. Defaults to "dagmc.h5m". @@ -395,13 +406,13 @@ def add_stp_file( self, filename: str, scale_factor: float = 1.0, - material_tags: typing.Optional[typing.Iterable[str]] = None, + material_tags: list[str] | None = None, ) -> int: """Loads the parts from stp file into the model. Args: filename: the filename used to save the html graph. - material_tags (typing.Iterable[str]): the names of the DAGMC + material_tags (list[str]): the names of the DAGMC material tags to assign. These will need to be in the same order as the volumes in the geometry added (STP file and CadQuery objects) and match the material tags @@ -424,17 +435,17 @@ def add_stp_file( def add_cadquery_object( self, - cadquery_object: typing.Union[ - cq.assembly.Assembly, cq.occ_impl.shapes.Compound, cq.occ_impl.shapes.Solid - ], - material_tags: typing.Optional[typing.Iterable[str]] = None, + cadquery_object: ( + cq.assembly.Assembly | cq.occ_impl.shapes.Compound | cq.occ_impl.shapes.Solid + ), + material_tags: list[str] | None, ) -> int: """Loads the parts from CadQuery object into the model. Args: cadquery_object: the cadquery object to convert, can be a CadQuery assembly cadquery workplane or a cadquery solid - material_tags (Optional typing.Iterable[str]): the names of the + material_tags (Optional list[str]): the names of the DAGMC material tags to assign. These will need to be in the same order as the volumes in the geometry added (STP file and CadQuery objects) and match the material tags used in the @@ -465,6 +476,7 @@ def export_unstructured_mesh_file( min_mesh_size: float = 1, max_mesh_size: float = 5, mesh_algorithm: int = 1, + method: str = "file", ): assembly = cq.Assembly() @@ -473,8 +485,12 @@ def export_unstructured_mesh_file( imprinted_assembly, _ = cq.occ_impl.assembly.imprint(assembly) - gmsh, _ = _mesh_brep( - occ_shape=imprinted_assembly.wrapped._address(), + gmsh = init_gmsh() + + gmsh, _ = get_volumes(gmsh, imprinted_assembly, method=method) + + gmsh = _mesh_brep( + gmsh=gmsh, min_mesh_size=min_mesh_size, max_mesh_size=max_mesh_size, mesh_algorithm=mesh_algorithm, @@ -499,6 +515,7 @@ def export_gmsh_mesh_file( max_mesh_size: float = 5, mesh_algorithm: int = 1, dimensions: int = 2, + method: str = "file", ): """Saves a GMesh msh file of the geometry in either 2D surface mesh or 3D volume mesh. @@ -510,6 +527,14 @@ def export_gmsh_mesh_file( mesh_algorithm: the gmsh mesh algorithm to use. dimensions: The number of dimensions, 2 for a surface mesh 3 for a volume mesh. Passed to gmsh.model.mesh.generate() + method: the method to use to import the geometry into gmsh. Options + are 'file' or 'in memory'. 'file' is the default and will write + the geometry to a temporary file before importing it into gmsh. + 'in memory' will import the geometry directly into gmsh but + requires the version of OpenCASCADE used to build gmsh to be + the same as the version used by CadQuery. This is possible to + ensure when installing the package with Conda but harder when + installing from PyPI. """ assembly = cq.Assembly() @@ -518,8 +543,12 @@ def export_gmsh_mesh_file( imprinted_assembly, _ = cq.occ_impl.assembly.imprint(assembly) - gmsh, _ = _mesh_brep( - occ_shape=imprinted_assembly.wrapped._address(), + gmsh = init_gmsh() + + gmsh, _ = get_volumes(gmsh, imprinted_assembly, method=method) + + gmsh = _mesh_brep( + gmsh=gmsh, min_mesh_size=min_mesh_size, max_mesh_size=max_mesh_size, mesh_algorithm=mesh_algorithm, @@ -538,7 +567,8 @@ def export_dagmc_h5m_file( min_mesh_size: float = 1, max_mesh_size: float = 5, mesh_algorithm: int = 1, - implicit_complement_material_tag: typing.Optional[str] = None, + implicit_complement_material_tag: str | None = None, + method: str = "file", ) -> str: """Saves a DAGMC h5m file of the geometry @@ -548,10 +578,17 @@ def export_dagmc_h5m_file( min_mesh_size (float, optional): the minimum size of mesh elements to use. Defaults to 1. max_mesh_size (float, optional): the maximum size of mesh elements to use. Defaults to 5. mesh_algorithm (int, optional): the GMSH mesh algorithm to use.. Defaults to 1. - implicit_complement_material_tag (typing.Optional[str], optional): + implicit_complement_material_tag (str | None, optional): the name of the material tag to use for the implicit complement (void space). Defaults to None which is a vacuum. Defaults to None. - + method: the method to use to import the geometry into gmsh. Options + are 'file' or 'in memory'. 'file' is the default and will write + the geometry to a temporary file before importing it into gmsh. + 'in memory' will import the geometry directly into gmsh but + requires the version of OpenCASCADE used to build gmsh to be + the same as the version used by CadQuery. This is possible to + ensure when installing the package with Conda but harder when + installing from PyPI. Returns: str: the DAGMC filename saved """ @@ -578,8 +615,12 @@ def export_dagmc_h5m_file( _check_material_tags(material_tags_in_brep_order, self.parts) - gmsh, volumes = _mesh_brep( - occ_shape=imprinted_assembly.wrapped._address(), # in memory address + gmsh = init_gmsh() + + gmsh, volumes = get_volumes(gmsh, imprinted_assembly, method=method) + + gmsh = _mesh_brep( + gmsh=gmsh, min_mesh_size=min_mesh_size, max_mesh_size=max_mesh_size, mesh_algorithm=mesh_algorithm, diff --git a/tests/test_python_api.py b/tests/test_python_api.py index 50facf1..e8039d5 100644 --- a/tests/test_python_api.py +++ b/tests/test_python_api.py @@ -116,7 +116,7 @@ def test_add_cadquery_object_returned_volumes(): sphere3 = cq.Workplane().moveTo(-100, -100).sphere(20) c2d = CadToDagmc() - vols = c2d.add_cadquery_object(sphere1) + vols = c2d.add_cadquery_object(sphere1, material_tags=["mat1"]) assert vols == 1 assembly = cq.Assembly() @@ -124,7 +124,7 @@ def test_add_cadquery_object_returned_volumes(): assembly.add(sphere2) assembly.add(sphere3) c2d = CadToDagmc() - vols = c2d.add_cadquery_object(assembly) + vols = c2d.add_cadquery_object(assembly, material_tags=["mat1", "mat2", "mat3"]) assert vols == 3