diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..2e98ce95 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: CI testing + +on: + pull_request: + branches: + - dev + - main + push: + branches: + - main + +jobs: + testing: + name: CI (Python=${{ matrix.python-version }}, OS=${{ matrix.os }}) + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash -el {0} + strategy: + matrix: + os: ["ubuntu-latest", "macos-latest", "windows-latest"] + python-version: ["3.11"] + + include: + - python-version: "3.8" + os: "ubuntu-latest" + - python-version: "3.9" + os: "ubuntu-latest" + - python-version: "3.10" + os: "ubuntu-latest" + steps: + - name: checkout actions + uses: actions/checkout@v4 + - uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + channels: conda-forge + + - name: install dependencies + run: conda install -c conda-forge freecad -y + + - name: install package + run: | + python -m pip install --upgrade pip + python -m pip install . + python -c 'import GEOUNED' + python -c 'from GEOReverse import reverse' + python -m pip install .[tests] + + - name: testing GEOUNED functionality + run: python -m pytest -v tests/test_convert.py + + - name: install openmc + if: ${{ matrix.os == 'ubuntu-latest'}} + run: conda install -c conda-forge openmc -y + + - name: testing with OpenMC + if: ${{ matrix.os == 'ubuntu-latest'}} + run: | + python -m pytest -v tests/test_volumes.py + python -m pytest -v tests/test_transport.py diff --git a/.gitignore b/.gitignore index 438ccede..28ce5fdd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,174 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# openmc output files +*.h5 +*.xml + +# testing folder +tests_outputs/ + .vscode **/__pycache__ -.coverage -htmlcov/* docs/source/_build/** docs/build/** docs/source/_autosummary/* docs/jupyter_execute -.pytest_cache/** -build/ \ No newline at end of file diff --git a/README.md b/README.md index d1e43e8b..abd2f2db 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![CI testing](https://github.com/GEOUNED-org/GEOUNED/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/GEOUNED-org/GEOUNED/actions/workflows/ci.yml) + # GEOUNED A tool to convert from CAD to CSG & CSG to CAD for Monte Carlo transport codes (MCNP & OpenMC). This repository contains the implementation of the algorithm presented in the paper [GEOUNED: A new conversion tool from CAD to Monte Carlo geometry](https://doi.org/10.1016/j.net.2024.01.052). @@ -27,7 +29,7 @@ In that case you can install directly de module using: C:\Program Files\FreeCAD 0.XX\bin\python.exe -m pip install git+https://github.com/GEOUNED-code/GEOUNED.git ``` using this option you have directly access to both FreeCAD and GEOUNED python modules. -Furthermore, using this python compabilities problems between different versions of python are avoided (some dynamic libraries of FreeCAD depends on the version of python used during the built process). +Furthermore, using this python compatibilities problems between different versions of python are avoided (some dynamic libraries of FreeCAD depends on the version of python used during the built process). ## How to use diff --git a/docs/Training_on_GEOUNED_tool.pdf b/docs/Training_on_GEOUNED_tool.pdf new file mode 100644 index 00000000..6e80849f Binary files /dev/null and b/docs/Training_on_GEOUNED_tool.pdf differ diff --git a/pyproject.toml b/pyproject.toml index dc683662..80ffd37a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "GEOUNED" -version = "1.0.0" +version = "1.0.1" authors = [ { name="Juan-Pablo Catalan", email="jpcatalan@ind.uned.es" }, { name="Patrick Sauvan", email="psauvan@ind.uned.es" }, @@ -22,6 +22,11 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/GEOUNED-code" -Repository = "https://github.com/GEOUNED-code/GEOUNED" -Documentation = "https://github.com/GEOUNED-code/GEOUNED/docs" +Homepage = "https://github.com/GEOUNED-org" +Repository = "https://github.com/GEOUNED-org/GEOUNED" +Documentation = "https://github.com/GEOUNED-org/GEOUNED/docs" + +[project.optional-dependencies] +tests = [ + "pytest", +] diff --git a/scripts/colorCADmat.py b/scripts/colorCADmat.py new file mode 100644 index 00000000..8d95e2d1 --- /dev/null +++ b/scripts/colorCADmat.py @@ -0,0 +1,50 @@ +import re +import sys + +sys.path.append('/usr/lib64/freecad/lib64/') +import ImportGui + +bd = 255 +gd = 255*255 +rd = 255* 255* 255 +matNumber=re.compile(r"Material_(?P\d+)") + +def getColor(num,vmin=0,vmax=rd): + colRange = rd + dnum = vmax - vmin + dx = num - vmin + scale = colRange/dnum + snum = dx * scale + 1 + + red = snum//gd + r = snum - red * gd + green = r // bd + blue = r - green * bd + return (red/255,green/255,blue/255) + +def setColorMaterial(documents): + featureObj = [] + matMin = 999999999 + matMax = 0 + for obj in documents.Objects: + if obj.TypeId == 'Part::Feature' : + for o in obj.InListRecursive: + m = matNumber.search(o.Label) + if m : + mat = int(m.group('matnum')) + matMin = min(matMin,mat) + matMax = max(matMax,mat) + featureObj.append((obj,mat)) + + for obj,mat in featureObj: + obj.ViewObject.ShapeColor = getColor(mat,matMin,matMax) + + +# color solids in a Freecad document with respect to the material number +doc = App.ActiveDocument +setColorMaterial(doc) +name = doc.Label +outname = name+'_color' +ImportGui.export(doc.RootObjects,outname+'.stp') +doc.saveAs(outname+'.FCStd') + diff --git a/scripts/geouned b/scripts/geouned index 2a077e1d..f8f0458a 100644 --- a/scripts/geouned +++ b/scripts/geouned @@ -32,11 +32,9 @@ elif len(sys.argv) == 3: runReverse = True inifile = sys.argv[1] else: - print('Bad option') - exit() + raise ValueError('Bad option') else: - print('Too many input arguments') - exit() + raise ValueError('Too many input arguments') if not runReverse : diff --git a/scripts/geouned.py b/scripts/geouned.py index ab85e14f..d94c2d82 100644 --- a/scripts/geouned.py +++ b/scripts/geouned.py @@ -31,12 +31,10 @@ runReverse = True inifile = sys.argv[1] else: - print('Bad option') - exit() -else: - print('Too many input arguments') - exit() + raise ValueError('Bad option') +else: + raise ValueError('Too many input arguments') if not runReverse : GEO = GEOUNED.GEOUNED(inifile) diff --git a/src/GEOReverse/Modules/MCNPinput.py b/src/GEOReverse/Modules/MCNPinput.py index 3f88cf82..55a26bb4 100644 --- a/src/GEOReverse/Modules/MCNPinput.py +++ b/src/GEOReverse/Modules/MCNPinput.py @@ -1,20 +1,20 @@ +import math import os -import sys import re + import FreeCAD -from GEOReverse.Modules.Parser import parser as mp -from GEOReverse.Modules.remh import cell_card_string, remove_hash -from GEOReverse.Modules.Objects import * -import math import numpy as np from numpy import linalg as LA +from .Objects import * +from .Parser import parser as mp +from .remh import cell_card_string, remove_hash + + class MCNPinput: def __init__(self,name): if not os.path.isfile(name): - print("File %s does not exist" %name) - sys.exit() - return + raise FileNotFoundError (f"File {name} does not exist") self.__inputcards__ = list(mp.get_cards(name)) self.Transformations = self.__getTransList__() return diff --git a/src/GEOReverse/Modules/Objects.py b/src/GEOReverse/Modules/Objects.py index bfcdaf61..3bf72afb 100644 --- a/src/GEOReverse/Modules/Objects.py +++ b/src/GEOReverse/Modules/Objects.py @@ -1,10 +1,13 @@ -from GEOReverse.Modules.buildSolidCell import BuildSolid -import FreeCAD, Part -import numpy as np import math -from GEOReverse.Modules.remh import cline -from GEOReverse.Modules.Utils.booleanFunction import outterTerms,BoolSequence -from GEOReverse.Modules.Utils.BooleanSolids import buildCTableFromSolids + +import FreeCAD +import numpy as np +import Part + +from .buildSolidCell import BuildSolid +from .remh import cline +from .Utils.booleanFunction import BoolSequence, outterTerms + class CADCell: def __init__(self,stringCell=None): diff --git a/src/GEOReverse/Modules/Parser/PartialFormatter.py b/src/GEOReverse/Modules/Parser/PartialFormatter.py index f4d050cf..2b08e050 100644 --- a/src/GEOReverse/Modules/Parser/PartialFormatter.py +++ b/src/GEOReverse/Modules/Parser/PartialFormatter.py @@ -1,4 +1,5 @@ import string + version = '3.6' def make_label(item): diff --git a/src/GEOReverse/Modules/Parser/parser.py b/src/GEOReverse/Modules/Parser/parser.py index 6c4bec26..914e6379 100644 --- a/src/GEOReverse/Modules/Parser/parser.py +++ b/src/GEOReverse/Modules/Parser/parser.py @@ -4,10 +4,11 @@ Functions for parsing MCNP input files. """ +import os import re import warnings -import os -from GEOReverse.Modules.Parser.PartialFormatter import PartialFormatter + +from .PartialFormatter import PartialFormatter version = '3.6' diff --git a/src/GEOReverse/Modules/Utils/BooleanSolids.py b/src/GEOReverse/Modules/Utils/BooleanSolids.py index c0e72a03..2fcf28f2 100644 --- a/src/GEOReverse/Modules/Utils/BooleanSolids.py +++ b/src/GEOReverse/Modules/Utils/BooleanSolids.py @@ -2,11 +2,12 @@ # Conversion to MCNP v0.0 # Only one solid and planar surfaces # -import FreeCAD -import BOPTools.SplitAPI import math -import re -from GEOReverse.Modules.Utils.booleanFunction import BoolSequence + +import BOPTools.SplitAPI +import FreeCAD + +from .booleanFunction import BoolSequence BoolVals = (None,True,False) @@ -481,7 +482,7 @@ def checkSign(solid,surf): apex,axis,t,dbl = surf.params r = point - apex r.normalize() - z = axis.dot(r) + z = round(axis.dot(r),15) alpha = math.acos(z) if alpha > math.atan(t) : diff --git a/src/GEOReverse/Modules/Utils/booleanFunction.py b/src/GEOReverse/Modules/Utils/booleanFunction.py index 947b5dda..fd2d944b 100644 --- a/src/GEOReverse/Modules/Utils/booleanFunction.py +++ b/src/GEOReverse/Modules/Utils/booleanFunction.py @@ -1,4 +1,5 @@ import re + mostinner=re.compile(r"\([^\(^\)]*\)") # identify most inner parentheses number =re.compile(r"(?P[-+]?\d+)") mix =re.compile(r"(?P([-+]?\d+|\[0+\]))") diff --git a/src/GEOReverse/Modules/XMLParser.py b/src/GEOReverse/Modules/XMLParser.py index 20113b3b..55f7c0af 100644 --- a/src/GEOReverse/Modules/XMLParser.py +++ b/src/GEOReverse/Modules/XMLParser.py @@ -1,4 +1,5 @@ -from GEOReverse.Modules.remh import cline +from .remh import cline + class cellCARD: diff --git a/src/GEOReverse/Modules/XMLinput.py b/src/GEOReverse/Modules/XMLinput.py index 91d2d4c7..a801e557 100644 --- a/src/GEOReverse/Modules/XMLinput.py +++ b/src/GEOReverse/Modules/XMLinput.py @@ -1,21 +1,20 @@ +import math import os -import sys import re +import xml.etree.ElementTree as ET + import FreeCAD -from GEOReverse.Modules.remh import cell_card_string, remove_hash -from GEOReverse.Modules.Objects import * -from GEOReverse.Modules.XMLParser import get_cards -import math import numpy as np from numpy import linalg as LA -import xml.etree.ElementTree as ET + +from .Objects import * +from .XMLParser import get_cards + class XMLinput: def __init__(self,name): if not os.path.isfile(name): - print("File %s does not exist" %name) - sys.exit() - return + raise FileNotFoundError(f"File {name} does not exist") tree = ET.parse(name) root = tree.getroot() diff --git a/src/GEOReverse/Modules/buildCAD.py b/src/GEOReverse/Modules/buildCAD.py index aadcadf0..8a035948 100644 --- a/src/GEOReverse/Modules/buildCAD.py +++ b/src/GEOReverse/Modules/buildCAD.py @@ -1,9 +1,7 @@ -import FreeCAD, Part import BOPTools.SplitAPI -from GEOReverse.Modules.buildSolidCell import FuseSolid -from GEOReverse.Modules.Utils.BooleanSolids import buildCTableFromSolids -from GEOReverse.Modules.Utils.booleanFunction import BoolSequence +from .buildSolidCell import FuseSolid +from .Utils.booleanFunction import BoolSequence def buildCAD(UnivCell,data,config): diff --git a/src/GEOReverse/Modules/buildSolidCell.py b/src/GEOReverse/Modules/buildSolidCell.py index dbd524bf..78d579eb 100644 --- a/src/GEOReverse/Modules/buildSolidCell.py +++ b/src/GEOReverse/Modules/buildSolidCell.py @@ -1,12 +1,8 @@ -import FreeCAD, Part -import BOPTools.SplitAPI -from BOPTools import GeneralFuseResult as GFR -from GEOReverse.Modules.Utils.booleanFunction import BoolSequence -from GEOReverse.Modules.splitFunction import SplitSolid,splitBase,joinBase -from GEOReverse.Modules.Utils.BooleanSolids import buildCTableFromSolids -from GEOReverse.Modules.options import Options - -import math +import Part + +from .options import Options +from .splitFunction import SplitSolid, joinBase, splitBase +from .Utils.booleanFunction import BoolSequence def getPart(slist): diff --git a/src/GEOReverse/Modules/processInp.py b/src/GEOReverse/Modules/processInp.py index 5b02181b..3f300bb3 100644 --- a/src/GEOReverse/Modules/processInp.py +++ b/src/GEOReverse/Modules/processInp.py @@ -1,5 +1,6 @@ import configparser -from GEOReverse.Modules.options import Options + +from .options import Options def setOptions(optionFile): @@ -25,8 +26,7 @@ def setOptions(optionFile): fileData = False for section in config.sections(): if section not in sectionNames: - print('{} bad section name'.format(section)) - exit() + raise ValueError(f'{section} bad section name') if section == 'Setting' : fileIn,fileOut,outBox,inFormat = getSetting(config) @@ -68,9 +68,7 @@ def setOptions(optionFile): setSecOptions(config) if not fileData: - print('missing input data in [Setting] section') - exit() - + raise ValueError('missing input data in [Setting] section') return setting @@ -143,14 +141,12 @@ def getRange(section,config): def getBoxData(string): data = tuple(map(float,string.split())) if len(data) != 6 : - print('bad Outbox value number') - exit() + raise ValueError('bad Outbox value number') elif( (data[0] > data[1]) or (data[2] > data[3]) or (data[4] > data[5]) ) : - print('bad Outbox boundaries') - exit() + raise ValueError('bad Outbox boundaries') else: return (10*data[0],10*data[2],10*data[4],10*data[1],10*data[3],10*data[5]) diff --git a/src/GEOReverse/Modules/remh.py b/src/GEOReverse/Modules/remh.py index 39df3f78..701b322b 100644 --- a/src/GEOReverse/Modules/remh.py +++ b/src/GEOReverse/Modules/remh.py @@ -1,8 +1,5 @@ -import sys -import re import math - - +import re ######################################### # define patterns to be found in string # diff --git a/src/GEOReverse/Modules/splitFunction.py b/src/GEOReverse/Modules/splitFunction.py index 4b5902a7..8dbad941 100644 --- a/src/GEOReverse/Modules/splitFunction.py +++ b/src/GEOReverse/Modules/splitFunction.py @@ -1,8 +1,9 @@ -import FreeCAD,Part -import BOPTools.SplitAPI -from GEOReverse.Modules.Utils.booleanFunction import outterTerms import math +import BOPTools.SplitAPI +import FreeCAD +import Part + class splitBase: def __init__(self,base,knownSurf={}): diff --git a/src/GEOReverse/__init__.py b/src/GEOReverse/__init__.py index e69de29b..3d9862c7 100644 --- a/src/GEOReverse/__init__.py +++ b/src/GEOReverse/__init__.py @@ -0,0 +1,8 @@ +# this try except attempts to import freecad (lowercase) which is the conda +# package name for FreeCAD (mixed case) upon import the conda package appends +# the sys path for Conda installed FreeCAD, consequently FreeCAD can then be +# found by subsequent import statements through out the code base +try: + import freecad +except: + pass diff --git a/src/GEOReverse/reverse.py b/src/GEOReverse/reverse.py index c90f92b8..3b53505d 100644 --- a/src/GEOReverse/reverse.py +++ b/src/GEOReverse/reverse.py @@ -1,12 +1,12 @@ -import sys -import FreeCAD, Part, Import +import FreeCAD +import Import -from GEOReverse.Modules.MCNPinput import MCNPinput -from GEOReverse.Modules.XMLinput import XMLinput -from GEOReverse.Modules.buildCAD import buildCAD,makeTree -from GEOReverse.Modules.Objects import CADCell -from GEOReverse.Modules.processInp import setOptions -from GEOReverse.CodeVersion import * +from .CodeVersion import * +from .Modules.buildCAD import buildCAD, makeTree +from .Modules.MCNPinput import MCNPinput +from .Modules.Objects import CADCell +from .Modules.processInp import setOptions +from .Modules.XMLinput import XMLinput def reverse(optFile='configRevese.ini') : @@ -30,14 +30,17 @@ def reverse(optFile='configRevese.ini') : UnivCell = CADCell() UnivCell.shape = UnivCell.makeBox(FreeCAD.BoundBox(*outBox)) -#get geometry definition from MCNP input + # get geometry definition from MCNP input if inFormat == 'mcnp': geom = MCNPinput(geomfile) elif inFormat == 'openMC_XML': geom = XMLinput(geomfile) else: - print('input format type {} not supported'.format(inFormat)) - exit() + msg = ( + f'input format type {inFormat} is not supported.' + 'Supported options are "mcnp" or "openMC_XML"' + ) + raise ValueError(msg) CADCells,fails = buildCAD(UnivCell,geom,CADselection) diff --git a/src/GEOUNED/Conversion/CellDefinition.py b/src/GEOUNED/Conversion/CellDefinition.py index 1e2cda97..ca390815 100644 --- a/src/GEOUNED/Conversion/CellDefinition.py +++ b/src/GEOUNED/Conversion/CellDefinition.py @@ -1,21 +1,22 @@ ############################ # Module for Cell definiton # ############################# -import FreeCAD, Part -from GEOUNED.Utils.Functions import GEOUNED_Surface -from GEOUNED.Utils.BasicFunctions_part1 \ - import isParallel, isOposite, isInLine, signPlane, isSameValue -from GEOUNED.Utils.booleanFunction import BoolSequence, insertInSequence - -import GEOUNED.Utils.BasicFunctions_part2 as BF -import GEOUNED.Utils.Functions as UF -import GEOUNED.Utils.Qform as QF -import GEOUNED.Utils.Geometry_GU as GU -from GEOUNED.Utils.Options.Classes import Tolerances as tol -from GEOUNED.Utils.Options.Classes import Options as opt -from GEOUNED.Utils.BooleanSolids import buildCTableFromSolids,removeExtraSurfaces import math -from collections import OrderedDict + +import FreeCAD +import Part + +from ..Utils import BasicFunctions_part2 as BF +from ..Utils import Functions as UF +from ..Utils import Geometry_GU as GU +from ..Utils.BasicFunctions_part1 import (isInLine, isOposite, isParallel, + isSameValue, signPlane) +from ..Utils.booleanFunction import BoolSequence, insertInSequence +from ..Utils.BooleanSolids import buildCTableFromSolids, removeExtraSurfaces +from ..Utils.Functions import GEOUNED_Surface +from ..Utils.Options.Classes import Options as opt +from ..Utils.Options.Classes import Tolerances as tol + def getId(facein, Surfaces): diff --git a/src/GEOUNED/Cuboid/translate.py b/src/GEOUNED/Cuboid/translate.py index 567237eb..9db5881c 100644 --- a/src/GEOUNED/Cuboid/translate.py +++ b/src/GEOUNED/Cuboid/translate.py @@ -1,12 +1,13 @@ -from GEOUNED.Utils.booleanFunction import BoolSequence -import GEOUNED.Utils.Geometry_GU as GU -from GEOUNED.Utils.BasicFunctions_part1 \ - import isParallel, isOposite, isInLine, signPlane, isSameValue -import GEOUNED.Utils.BasicFunctions_part2 as BF -import GEOUNED.Decompose.Decom_one as Decom -from GEOUNED.Utils.Options.Classes import Tolerances as tol -from GEOUNED.Utils.Options.Classes import Options as opt -import FreeCAD,Part +import FreeCAD +import Part + +from ..Decompose import Decom_one as Decom +from ..Utils import BasicFunctions_part2 as BF +from ..Utils import Geometry_GU as GU +from ..Utils.BasicFunctions_part1 import isOposite, isParallel +from ..Utils.booleanFunction import BoolSequence +from ..Utils.Options.Classes import Options as opt +from ..Utils.Options.Classes import Tolerances as tol def commonEdge(face1,face2): @@ -49,8 +50,6 @@ def isInverted(solid): u=(Range[1]+Range[0])/2.0 v=(Range[3]+Range[2])/2.0 - dist1=face.CenterOfMass.distanceToPoint(solid.BoundBox.Center) - dist2=face.CenterOfMass.add(face.normalAt(u,v).multiply(1.0e-6)).distanceToPoint(solid.BoundBox.Center) point2=face.CenterOfMass.add(face.normalAt(u,v).multiply(1.0e-6)) if (solid.isInside(point2,1e-7,False)): @@ -61,7 +60,6 @@ def isInverted(solid): def getId(facein, Surfaces): - surfin = str(facein) if isParallel(facein.Axis,FreeCAD.Vector(1,0,0),tol.pln_angle) : P = 'PX' elif isParallel(facein.Axis,FreeCAD.Vector(0,1,0),tol.pln_angle) : diff --git a/src/GEOUNED/Decompose/Decom_one.py b/src/GEOUNED/Decompose/Decom_one.py index 80ca97c2..f0f57bf3 100644 --- a/src/GEOUNED/Decompose/Decom_one.py +++ b/src/GEOUNED/Decompose/Decom_one.py @@ -2,18 +2,20 @@ # Only one solid and planar surfaces # -import FreeCAD, Part -import GEOUNED.Utils.Functions as UF -import GEOUNED.Utils.Geometry_GU as GU -import GEOUNED.Conversion.CellDefinition as CD -from collections import OrderedDict -from GEOUNED.Utils.BasicFunctions_part1 import isParallel, isSameValue,\ - isInLine, isInPlane -from GEOUNED.Utils.BasicFunctions_part2 import isDuplicateInList -from GEOUNED.Utils.Options.Classes import Tolerances as tol -from GEOUNED.Utils.Options.Classes import Options as opt import math -import copy +from collections import OrderedDict + +import FreeCAD +import Part + +from ..Conversion import CellDefinition as CD +from ..Utils import Functions as UF +from ..Utils import Geometry_GU as GU +from ..Utils.BasicFunctions_part1 import (isInLine, isInPlane, isParallel, + isSameValue) +from ..Utils.BasicFunctions_part2 import isDuplicateInList +from ..Utils.Options.Classes import Options as opt +from ..Utils.Options.Classes import Tolerances as tol twoPi = math.pi*2 diff --git a/src/GEOUNED/LoadFile/LoadFunctions.py b/src/GEOUNED/LoadFile/LoadFunctions.py index 4f8d60c0..45c942b5 100644 --- a/src/GEOUNED/LoadFile/LoadFunctions.py +++ b/src/GEOUNED/LoadFile/LoadFunctions.py @@ -1,7 +1,9 @@ import re + import FreeCAD -from GEOUNED.Utils.Functions import GEOUNED_Solid -from GEOUNED.Utils.Options.Classes import Options as opt + +from ..Utils.Functions import GEOUNED_Solid +from ..Utils.Options.Classes import Options as opt def GetLabel(label): @@ -70,10 +72,11 @@ def checkCADFileMaterial(Material, mdict): """Function to check that if a component in the CAD file has a material, it exist in the material file""" templist = [ elem for elem in set(Material) if elem not in mdict ] if templist: - print('At least one material in the CAD model is not present in the material file') - print('List of not present materials:', templist) - print('Code STOPS') - exit() + raise ValueError( + 'At least one material in the CAD model is not present in the material file\n' + f'List of not present materials:{templist}\n' + 'Code STOPS\n' + ) return # Paco mod @@ -195,8 +198,8 @@ def checkEnclosure(FreeCAD_doc,EnclosureList): print(lab) # this stop should not be move to the end of routine - # if previous point not satisfied point 4) may lead to infite loop - if stop : exit() + # if previous point not satisfied point 4) may lead to infinite loop + if stop : raise ValueError('Exiting to avoid infinite loop') # 4) look for explicit loops enclDict = dict() @@ -223,7 +226,7 @@ def checkEnclosure(FreeCAD_doc,EnclosureList): for p in enclDict.keys(): for s in enclDict[p] : print ('{}_{}'.format(s,p)) - exit() + raise ValueError('GEOUNED.LoadFunctions.checkEnclosure failed') # check enclosures solids are correctly nested and don't overlap each other nestedLevels = setEnclosureLevels(EnclosureList) @@ -261,7 +264,7 @@ def checkEnclosure(FreeCAD_doc,EnclosureList): for elemt in overlap: print('{}_{}').format(elemt[0],elemt[1]) - if stop : exit() + if stop : raise ValueError('GEOUNED.LoadFunctions.checkEnclosure failed') def checkOverlap(enclosures): overlap = [] diff --git a/src/GEOUNED/LoadFile/LoadSTEP.py b/src/GEOUNED/LoadFile/LoadSTEP.py index 59dd8990..9deaa28b 100644 --- a/src/GEOUNED/LoadFile/LoadSTEP.py +++ b/src/GEOUNED/LoadFile/LoadSTEP.py @@ -1,12 +1,15 @@ # # Module to load a STEP file # -import FreeCAD, Part -from FreeCAD import Import +import os import re -import os.path -import GEOUNED.Utils.Functions as UF -import GEOUNED.LoadFile.LoadFunctions as LF + +import FreeCAD +import Part +from FreeCAD import Import + +from ..Utils import Functions as UF +from . import LoadFunctions as LF # Paco mod diff --git a/src/GEOUNED/Utils/BasicFunctions_part1.py b/src/GEOUNED/Utils/BasicFunctions_part1.py index 0ade2170..0b455fa4 100644 --- a/src/GEOUNED/Utils/BasicFunctions_part1.py +++ b/src/GEOUNED/Utils/BasicFunctions_part1.py @@ -1,9 +1,11 @@ # # Set of useful functions used in different parts of the code # -import FreeCAD import math +import FreeCAD + + def isSameValue(v1,v2,tolerance=1e-6): return abs(v1-v2) < tolerance diff --git a/src/GEOUNED/Utils/BasicFunctions_part2.py b/src/GEOUNED/Utils/BasicFunctions_part2.py index 0b4b0f6e..b80b322e 100644 --- a/src/GEOUNED/Utils/BasicFunctions_part2.py +++ b/src/GEOUNED/Utils/BasicFunctions_part2.py @@ -1,33 +1,36 @@ # # Set of useful functions used in different parts of the code # -from GEOUNED.Utils.BasicFunctions_part1 \ - import isParallel, isOposite,isSameValue, isInTolerance, isInLine, isInPlane -from GEOUNED.Write.Functions import MCNPSurface -import FreeCAD import math +import FreeCAD + +from ..Utils.BasicFunctions_part1 import (isInLine, isInPlane, isInTolerance, + isOposite, isParallel, isSameValue) +from ..Write.Functions import MCNPSurface sameSurfFic = open('fuzzySurfaces','w') -def Fuzzy(index,dtype,surf1,surf2,val): +def Fuzzy(index,dtype,surf1,surf2,val,tol): + + same = val <= tol if dtype == 'plane': p1str = MCNPSurface(index,'Plane',surf1) p2str = MCNPSurface(0,'Plane',surf2) - line = 'Plane distance : {} \n {}\n {}\n\n'.format(val,p1str,p2str) + line = 'Same surface : {}\nPlane distance / Tolerance : {} {}\n {}\n {}\n\n'.format(same,val,tol,p1str,p2str) sameSurfFic.write(line) elif dtype == 'cylRad': cyl1str = MCNPSurface(index,'Cylinder',surf1) cyl2str = MCNPSurface(0,'Cylinder',surf2) - line = 'Diff Radius: {} \n {}\n {}\n\n'.format(val,cyl1str,cyl2str) + line = 'Same surface : {}\nDiff Radius / Tolerance: {} {}\n {}\n {}\n\n'.format(same,val,tol,cyl1str,cyl2str) sameSurfFic.write(line) elif dtype == 'cylAxs': cyl1str = MCNPSurface(index,'Cylinder',surf1) cyl2str = MCNPSurface(0,'Cylinder',surf2) c12 = surf1.Center - surf2.Center - line = 'Dist Axis: {} \n {}\n {}\n\n'.format(val,cyl1str,cyl2str) + line = 'Same surface : {}\nDist Axis / Tolerance: {} {}\n {}\n {}\n\n'.format(same,val,tol,cyl1str,cyl2str) sameSurfFic.write(line) def isSamePlane(p1,p2,dtol=1e-6,atol=1e-6,relTol=True,fuzzy=(False,0)): @@ -42,7 +45,7 @@ def isSamePlane(p1,p2,dtol=1e-6,atol=1e-6,relTol=True,fuzzy=(False,0)): tol = dtol isSame,isFuzzy = isInTolerance(d,tol,0.5*tol,2*tol) - if isFuzzy and fuzzy[0]: Fuzzy(fuzzy[1],'plane',p2,p1,d) + if isFuzzy and fuzzy[0]: Fuzzy(fuzzy[1],'plane',p2,p1,d,tol) return isSame return False @@ -54,7 +57,7 @@ def isSameCylinder(cyl1,cyl2,dtol=1e-6, atol=1e-6, relTol=True, fuzzy=(False,0)) rtol = dtol isSameRad,isFuzzy = isInTolerance(cyl2.Radius-cyl1.Radius,rtol,0.5*rtol,2*rtol) - if isFuzzy and fuzzy[0]: Fuzzy(fuzzy[1],'cylRad',cyl2,cyl1,abs(cyl2.Radius-cyl1.Radius)) + if isFuzzy and fuzzy[0]: Fuzzy(fuzzy[1],'cylRad',cyl2,cyl1,abs(cyl2.Radius-cyl1.Radius),rtol) if isSameRad : if isParallel(cyl1.Axis,cyl2.Axis,atol): @@ -67,7 +70,7 @@ def isSameCylinder(cyl1,cyl2,dtol=1e-6, atol=1e-6, relTol=True, fuzzy=(False,0)) tol = dtol isSameCenter,isFuzzy = isInTolerance(d,tol,0.5*tol,2*tol) - if isFuzzy and fuzzy[0]: Fuzzy(fuzzy[1],'cylAxs',cyl1,cyl2,d) + if isFuzzy and fuzzy[0]: Fuzzy(fuzzy[1],'cylAxs',cyl1,cyl2,d,tol) return isSameCenter return False diff --git a/src/GEOUNED/Utils/BooleanSolids.py b/src/GEOUNED/Utils/BooleanSolids.py index a756b251..60ab1380 100644 --- a/src/GEOUNED/Utils/BooleanSolids.py +++ b/src/GEOUNED/Utils/BooleanSolids.py @@ -2,13 +2,13 @@ # Conversion to MCNP v0.0 # Only one solid and planar surfaces # -import FreeCAD import math -import re -from GEOUNED.Utils.Functions import GEOUNED_Surface, splitBOP -from GEOUNED.Utils.booleanFunction import BoolSequence -from GEOUNED.Utils.Options.Classes import Options as opt +import FreeCAD + +from ..Utils.booleanFunction import BoolSequence +from ..Utils.Functions import GEOUNED_Surface, splitBOP +from ..Utils.Options.Classes import Options as opt BoolVals = (None,True,False) @@ -531,7 +531,7 @@ def checkSign(solid,surf): elif surf.Type == 'Cone': r = point - surf.Surf.Apex r.normalize() - z = surf.Surf.Axis.dot(r) + z = round(surf.Surf.Axis.dot(r),15) alpha = math.acos(z) if alpha > surf.Surf.SemiAngle : diff --git a/src/GEOUNED/Utils/Functions.py b/src/GEOUNED/Utils/Functions.py index 2c9bd082..f5a34f63 100644 --- a/src/GEOUNED/Utils/Functions.py +++ b/src/GEOUNED/Utils/Functions.py @@ -1,16 +1,20 @@ # # Set of useful functions used in different parts of the code # -import BOPTools.SplitAPI -import GEOUNED.Utils.BasicFunctions_part2 as BF -from GEOUNED.Utils.BasicFunctions_part1 import isParallel,\ - Plane3PtsParams, PlaneParams, CylinderParams, ConeParams, SphereParams, TorusParams -from GEOUNED.Utils.Options.Classes import Tolerances as tol -from GEOUNED.Utils.Options.Classes import Options -import copy -import FreeCAD, Part import math + +import BOPTools.SplitAPI +import FreeCAD import numpy as np +import Part + +from ..Utils.BasicFunctions_part1 import (ConeParams, CylinderParams, + Plane3PtsParams, PlaneParams, + SphereParams, TorusParams, + isParallel) +from ..Utils.Options.Classes import Options +from ..Utils.Options.Classes import Tolerances as tol +from . import BasicFunctions_part2 as BF def getBox(comp): @@ -243,8 +247,7 @@ def __init__(self,params,boundBox,Face=None) : if Face == 'Build' : self.buildSurface() if self.shape == 'Build' : - print('stop',params,boundBox) - exit() + raise ValueError(f'stop {params} {boundBox}') return diff --git a/src/GEOUNED/Utils/Geometry_GU.py b/src/GEOUNED/Utils/Geometry_GU.py index cf46858a..769bab91 100644 --- a/src/GEOUNED/Utils/Geometry_GU.py +++ b/src/GEOUNED/Utils/Geometry_GU.py @@ -1,11 +1,14 @@ # # definition of GEOUNED objects to release memory # -import FreeCAD, Part import math -from GEOUNED.Utils.BasicFunctions_part1 import isSameValue -from GEOUNED.Utils.BasicFunctions_part2 import isSameTorus -from GEOUNED.Utils.Options.Classes import Tolerances as tol + +import Part + +from ..Utils.Options.Classes import Tolerances as tol +from .BasicFunctions_part1 import isSameValue +from .BasicFunctions_part2 import isSameTorus + # SURFACES class Surfaces_GU(object): @@ -185,8 +188,8 @@ def __mergePeriodicUV__(self,parameter,faceList): params.sort() V0 = params[0][0] V1 = params[-1][1] - if isSameValue(arcLength, twoPi, tol.relativePrecision ): - mergedParams = (True,(V0,V1)) + if arcLength >= twoPi*(1.-tol.relativePrecision): + mergedParams = (True,(V0,V0+twoPi)) else: if isSameValue(V0, 0., tol.relativePrecision ) and isSameValue(V1, twoPi, tol.relativePrecision ) : for i in range(len(params)-1): diff --git a/src/GEOUNED/Utils/Options/__init__.py b/src/GEOUNED/Utils/Options/__init__.py index f6437a78..8a43702e 100644 --- a/src/GEOUNED/Utils/Options/__init__.py +++ b/src/GEOUNED/Utils/Options/__init__.py @@ -1,5 +1,4 @@ -import os.path -from GEOUNED.Utils.Options.Classes import Options, Tolerances, MCNP_numeric_format +from .Classes import MCNP_numeric_format, Options, Tolerances #default options values forceCylinder = True # Force using cylinders instead cones for auxillary surfaces of torus surface definition diff --git a/src/GEOUNED/Utils/Qform.py b/src/GEOUNED/Utils/Qform.py index 792e912f..27ac1bf5 100644 --- a/src/GEOUNED/Utils/Qform.py +++ b/src/GEOUNED/Utils/Qform.py @@ -4,9 +4,12 @@ # # -import FreeCAD, Part import math +import FreeCAD +import Part + + def RotationMatrix(u,v): """ Definition of the rotation matrix for two vectors""" diff --git a/src/GEOUNED/Utils/booleanFunction.py b/src/GEOUNED/Utils/booleanFunction.py index 4c679157..4691cbb2 100644 --- a/src/GEOUNED/Utils/booleanFunction.py +++ b/src/GEOUNED/Utils/booleanFunction.py @@ -1,4 +1,5 @@ import re + mostinner=re.compile(r"\([^\(^\)]*\)") # identify most inner parentheses number =re.compile(r"(?P[-+]?\d+)") mix =re.compile(r"(?P([-+]?\d+|\[0+\]))") diff --git a/src/GEOUNED/Void/Void.py b/src/GEOUNED/Void/Void.py index af769b20..c7805e33 100644 --- a/src/GEOUNED/Void/Void.py +++ b/src/GEOUNED/Void/Void.py @@ -1,11 +1,14 @@ -from GEOUNED.Void.VoidBoxClass import VoidBox -from GEOUNED.Utils.BasicFunctions_part1 import isOposite -from GEOUNED.Utils.Functions import GEOUNED_Solid, GEOUNED_Surface -from GEOUNED.Utils.booleanFunction import BoolSequence -from GEOUNED.Utils.Options.Classes import Options as opt -import GEOUNED.LoadFile.LoadFunctions as LF -import GEOUNED.Void.voidFunctions as VF -import FreeCAD, Part +import FreeCAD +import Part + +from ..LoadFile import LoadFunctions as LF +from ..Utils.BasicFunctions_part1 import isOposite +from ..Utils.booleanFunction import BoolSequence +from ..Utils.Functions import GEOUNED_Solid, GEOUNED_Surface +from ..Utils.Options.Classes import Options as opt +from ..Void import voidFunctions as VF +from .VoidBoxClass import VoidBox + def voidGeneration(MetaList,EnclosureList,Surfaces,UniverseBox,setting,init): voidList = [] @@ -175,7 +178,7 @@ def getUniverseComplementary(Universe,Surfaces): def voidCommentLine(CellInfo): boxDef,cellIn = CellInfo cells = ', '.join(map(str,cellIn)) - box = ', '.join(map(str,boxDef)) - line = 'Automatic Generated Void Cell. Enclosure({})\n'.format(box) - line += 'Enclosed cells : ({})\n'.format(cells) + box = ', '.join(f'{num:.3f}' for num in boxDef) + line = f'Automatic Generated Void Cell. Enclosure({box})\n' + line += f'Enclosed cells : ({cells})\n' return line diff --git a/src/GEOUNED/Void/VoidBoxClass.py b/src/GEOUNED/Void/VoidBoxClass.py index 51cc9a00..9ead80e3 100644 --- a/src/GEOUNED/Void/VoidBoxClass.py +++ b/src/GEOUNED/Void/VoidBoxClass.py @@ -1,14 +1,14 @@ """File with the VoidBox class""" import FreeCAD import Part -import GEOUNED.Decompose.Decom_one as Decom -import GEOUNED.Conversion.CellDefinition as Conv - -from GEOUNED.Utils.Options.Classes import Options as opt -from GEOUNED.Utils.BasicFunctions_part1 import isOposite -from GEOUNED.Utils.Functions import GEOUNED_Solid, GEOUNED_Surface -from GEOUNED.Utils.booleanFunction import BoolSequence -from GEOUNED.Utils.BooleanSolids import buildCTableFromSolids, removeExtraSurfaces + +from ..Conversion import CellDefinition as Conv +from ..Decompose import Decom_one as Decom +from ..Utils.BasicFunctions_part1 import isOposite +from ..Utils.booleanFunction import BoolSequence +from ..Utils.BooleanSolids import buildCTableFromSolids, removeExtraSurfaces +from ..Utils.Functions import GEOUNED_Solid, GEOUNED_Surface +from ..Utils.Options.Classes import Options as opt class VoidBox (): diff --git a/src/GEOUNED/Write/Functions.py b/src/GEOUNED/Write/Functions.py index 8b6c237f..4094aa47 100644 --- a/src/GEOUNED/Write/Functions.py +++ b/src/GEOUNED/Write/Functions.py @@ -1,14 +1,16 @@ -import GEOUNED.Utils.Qform as Qform -from GEOUNED.Utils.BasicFunctions_part1 import isParallel, isOposite -from GEOUNED.Utils.Options.Classes import MCNP_numeric_format as nf -from GEOUNED.Utils.Options.Classes import Tolerances as tol -from GEOUNED.Utils.Options.Classes import Options as opt -from GEOUNED.Write.StringFunctions import remove_redundant - -import FreeCAD import math import re +import FreeCAD + +from ..Utils import Qform as Qform +from ..Utils.BasicFunctions_part1 import isOposite, isParallel +from ..Utils.Options.Classes import MCNP_numeric_format as nf +from ..Utils.Options.Classes import Options as opt +from ..Utils.Options.Classes import Tolerances as tol +from .StringFunctions import remove_redundant + + class CardLine : def __init__(self,card,linesize=80,tabspace=6,fmt=''): self.str = card + ' ' @@ -352,7 +354,7 @@ def OpenMCSurface(Type,surf,outXML=True,quadricForm=False): A=surf.Axis.x B=surf.Axis.y C=surf.Axis.z - D=surf.Axis.dot(surf.Position) + D=surf.Axis.dot(surf.Position)*0.1 if surf.Axis.isEqual(FreeCAD.Vector(1,0,0),tol.pln_angle) : if outXML : OMCsurf = 'x-plane' @@ -387,51 +389,52 @@ def OpenMCSurface(Type,surf,outXML=True,quadricForm=False): elif (Type == 'Cylinder'): - Pos = surf.Center + Pos = surf.Center*0.1 + Rad = surf.Radius*0.1 Dir = FreeCAD.Vector(surf.Axis) Dir.normalize() if (isParallel(Dir,FreeCAD.Vector(1,0,0),tol.angle)): if outXML : OMCsurf = 'x-cylinder' - coeffs = '{:{xy}} {:{xy}} {:{r}}'.format(Pos.y, Pos.z, surf.Radius, xy=nf.C_xyz, r=nf.C_r) + coeffs = '{:{xy}} {:{xy}} {:{r}}'.format(Pos.y, Pos.z, Rad, xy=nf.C_xyz, r=nf.C_r) else: OMCsurf = 'XCylinder' - coeffs = 'y0={},z0={},r={}'.format(Pos.y, Pos.z, surf.Radius) + coeffs = 'y0={},z0={},r={}'.format(Pos.y, Pos.z, Rad) elif (isParallel(Dir,FreeCAD.Vector(0,1,0),tol.angle)): if outXML : OMCsurf = 'y-cylinder' - coeffs = '{:{xy}} {:{xy}} {:{r}}'.format(Pos.x, Pos.z, surf.Radius, xy=nf.C_xyz, r=nf.C_r) + coeffs = '{:{xy}} {:{xy}} {:{r}}'.format(Pos.x, Pos.z, Rad, xy=nf.C_xyz, r=nf.C_r) else: OMCsurf = 'YCylinder' - coeffs = 'x0={},z0={},r={}'.format(Pos.x, Pos.z, surf.Radius) + coeffs = 'x0={},z0={},r={}'.format(Pos.x, Pos.z, Rad) elif (isParallel(Dir,FreeCAD.Vector(0,0,1),tol.angle)): if outXML : OMCsurf = 'z-cylinder' - coeffs = '{:{xy}} {:{xy}} {:{r}}'.format(Pos.x, Pos.y, surf.Radius, xy=nf.C_xyz, r=nf.C_r) + coeffs = '{:{xy}} {:{xy}} {:{r}}'.format(Pos.x, Pos.y, Rad, xy=nf.C_xyz, r=nf.C_r) else: OMCsurf = 'ZCylinder' - coeffs = 'x0={},y0={},r={}'.format(Pos.x, Pos.y, surf.Radius) + coeffs = 'x0={},y0={},r={}'.format(Pos.x, Pos.y, Rad) else: if outXML : OMCsurf = 'quadric' - Q=Qform.QFormCyl(Dir,Pos,surf.Radius) + Q=Qform.QFormCyl(Dir,Pos,Rad) coeffs='{v[0]:{aTof}} {v[1]:{aTof}} {v[2]:{aTof}} {v[3]:{aTof}} {v[4]:{aTof}} {v[5]:{aTof}} {v[6]:{gToi}} {v[7]:{gToi}} {v[8]:{gToi}} {v[9]:{j}}'\ .format(v=Q,aTof=nf.GQ_1to6,gToi=nf.GQ_7to9,j=nf.GQ_10) else: if quadricForm : OMCsurf = 'Quadric' - Q=Qform.QFormCyl(Dir,Pos,surf.Radius) + Q=Qform.QFormCyl(Dir,Pos,Rad) coeffs='a={v[0]},b={v[1]},c={v[2]},d={v[3]},e={v[4]},f={v[5]},g={v[6]},h={v[7]},j={v[8]},k={v[9]}'.format(v=Q) else : OMCsurf = 'Cylinder' - coeffs = 'x0={},y0={},z0={},r={},dx={},dy={},dz={}'.format(Pos.x, Pos.y, Pos.z, surf.Radius, Dir.x, Dir.y, Dir.z) + coeffs = 'x0={},y0={},z0={},r={},dx={},dy={},dz={}'.format(Pos.x, Pos.y, Pos.z, Rad, Dir.x, Dir.y, Dir.z) elif (Type == 'Cone'): - Apex = surf.Apex + Apex = surf.Apex*0.1 Dir = FreeCAD.Vector(surf.Axis) Dir.normalize() tan = math.tan(surf.SemiAngle) @@ -481,23 +484,28 @@ def OpenMCSurface(Type,surf,outXML=True,quadricForm=False): coeffs='x0={},y0={},z0={},r2={},dx={},dy={},dz={}'.format(Apex.x, Apex.y, Apex.z, tan2, Dir.x, Dir.y, Dir.z) elif (Type == 'Sphere'): + Center = surf.Center*0.1 + Rad = surf.Radius*0.1 if outXML : OMCsurf = 'sphere' - coeffs = '{:{xyz}} {:{xyz}} {:{xyz}} {:{r}}'.format(surf.Center.x, surf.Center.y, surf.Center.z, surf.Radius, xyz=nf.S_xyz, r=nf.S_r) + coeffs = '{:{xyz}} {:{xyz}} {:{xyz}} {:{r}}'.format(Center.x, Center.y, Center.z, Rad, xyz=nf.S_xyz, r=nf.S_r) else: OMCsurf = 'Sphere' - coeffs = 'x0={},y0={},z0={},r={}'.format(surf.Center.x, surf.Center.y, surf.Center.z, surf.Radius) + coeffs = 'x0={},y0={},z0={},r={}'.format(Center.x, Center.y, Center.z, Rad) elif (Type == 'Torus'): + Center = surf.Center*0.1 + minRad = surf.MinorRadius*0.1 + majRad = surf.MajorRadius*0.1 Dir = FreeCAD.Vector(surf.Axis) Dir.normalize() if outXML : - coeffs ='{:{xyz}} {:{xyz}} {:{xyz}} {:{r}} {:{r}} {:{r}}'.format(surf.Center.x, surf.Center.y, surf.Center.z, \ - surf.MajorRadius, surf.MinorRadius, surf.MinorRadius,\ + coeffs ='{:{xyz}} {:{xyz}} {:{xyz}} {:{r}} {:{r}} {:{r}}'.format(Center.x, Center.y, Center.z,\ + majRad, minRad, minRad,\ xyz=nf.T_xyz,r=nf.T_r) else: - coeffs ='x0={},y0={},z0={},r{},r1={},r2={}'.format(surf.Center.x, surf.Center.y, surf.Center.z, surf.MajorRadius, surf.MinorRadius, surf.MinorRadius) + coeffs ='x0={},y0={},z0={},r{},r1={},r2={}'.format(Center.x, Center.y, Center.z, majRad, minRad, minRad) if (isParallel(Dir,FreeCAD.Vector(1,0,0),tol.angle)): OMCsurf = 'x-torus' if outXML else 'XTorus' diff --git a/src/GEOUNED/Write/MCNPFormat.py b/src/GEOUNED/Write/MCNPFormat.py index 73e5a8ce..4695198d 100644 --- a/src/GEOUNED/Write/MCNPFormat.py +++ b/src/GEOUNED/Write/MCNPFormat.py @@ -1,19 +1,17 @@ ############################## # Module to write MCNP input # ############################## +import math from datetime import datetime -from GEOUNED.Utils.Functions import Surfaces_dict -from GEOUNED.Utils.BasicFunctions_part1 import pointsToCoeffs,isOposite -from GEOUNED.Write.Functions import MCNPSurface, changeSurfSign, writeMCNPCellDef,CardLine -from GEOUNED.Utils.Options.Classes import MCNP_numeric_format as nf -from GEOUNED.Utils.Options.Classes import Tolerances as tol -from GEOUNED.Utils.Options.Classes import Options as opt -from GEOUNED.CodeVersion import * import FreeCAD -import copy -import math -import re + +from ..CodeVersion import * +from ..Utils.BasicFunctions_part1 import isOposite, pointsToCoeffs +from ..Utils.Functions import Surfaces_dict +from ..Utils.Options.Classes import Options as opt +from .Functions import CardLine, MCNPSurface, changeSurfSign, writeMCNPCellDef + class MCNP_input: def __init__(self,Meta,Surfaces,setting) : diff --git a/src/GEOUNED/Write/OpenMCFormat.py b/src/GEOUNED/Write/OpenMCFormat.py index 469a4cd8..5a92b01c 100644 --- a/src/GEOUNED/Write/OpenMCFormat.py +++ b/src/GEOUNED/Write/OpenMCFormat.py @@ -1,17 +1,14 @@ ############################## # Module to write MCNP input # ############################## -from datetime import datetime -from GEOUNED.Utils.Functions import Surfaces_dict -from GEOUNED.Write.Functions import OpenMCSurface, changeSurfSign, writeOpenMCregion,CardLine -from GEOUNED.Utils.Options.Classes import MCNP_numeric_format as nf -from GEOUNED.Utils.Options.Classes import Options as opt -from GEOUNED.CodeVersion import * import FreeCAD -import copy -import math -import re + +from ..CodeVersion import * +from ..Utils.Functions import Surfaces_dict +from ..Utils.Options.Classes import Options as opt +from .Functions import OpenMCSurface, changeSurfSign, writeOpenMCregion + class OpenMC_input: def __init__(self,Meta,Surfaces,setting) : @@ -57,6 +54,7 @@ def __write_xml_surface_block__(self): def __write_xml_cells__(self,cell): """ Write the cell in xml OpenMC format """ index = cell.label + cellName = ". ".join(cell.Comments.splitlines()) if cell.__id__ is None : return if cell.Material == 0: @@ -64,9 +62,10 @@ def __write_xml_cells__(self,cell): else: matName = '{}'.format(cell.Material) - OMCcell = ' \n'.format( \ + OMCcell = ' \n'.format( \ index,\ matName,\ + cellName,\ writeOpenMCregion(cell.Definition,'XML') ) self.inpfile.write(OMCcell) return @@ -146,7 +145,7 @@ def __write_py_surface_block__(self): def __write_py_surfaces__(self,surface,boundary=False): - """ Write the surfaces in xml OpenMC format """ + """ Write the surfaces in python OpenMC format """ surfType,coeffs = OpenMCSurface(surface.Type, surface.Surf, outXML = False, quadricForm = opt.quadricPY) @@ -174,15 +173,16 @@ def __write_py_cell_block__(self): def __write_py_cells__(self,cell): - """ Write the cell in xml OpenMC format """ + """ Write the cell in python OpenMC format """ index = cell.label + cellName = ". ".join(cell.Comments.splitlines()) if cell.__id__ is None : return if cell.Material == 0: - OMCcell = 'C{} = openmc.Cell(region={})\n'.format(index, writeOpenMCregion(cell.Definition,'PY') ) + OMCcell = 'C{} = openmc.Cell(name="{}", region={})\n'.format(index, cellName, writeOpenMCregion(cell.Definition,'PY') ) else: matName = 'M{}'.format(cell.Material) - OMCcell = 'C{} = openmc.Cell(fill={}, region={})\n'.format(index, matName, writeOpenMCregion(cell.Definition,'PY') ) + OMCcell = 'C{} = openmc.Cell(name="{}", fill={}, region={})\n'.format(index, cellName, matName, writeOpenMCregion(cell.Definition,'PY') ) self.inpfile.write(OMCcell) return diff --git a/src/GEOUNED/Write/PHITSFormat.py b/src/GEOUNED/Write/PHITSFormat.py index a895bb62..5740d9c4 100644 --- a/src/GEOUNED/Write/PHITSFormat.py +++ b/src/GEOUNED/Write/PHITSFormat.py @@ -11,19 +11,18 @@ # 5. Eliminated the only MCNP related parts # 6. Added some comments to remind +import re from datetime import datetime -from GEOUNED.Utils.Functions import Surfaces_dict -from GEOUNED.Utils.BasicFunctions_part1 import pointsToCoeffs,isOposite -from GEOUNED.Write.Functions import PHITSSurface, changeSurfSign, writePHITSCellDef,CardLine,CellString -from GEOUNED.Utils.Options.Classes import MCNP_numeric_format as nf -from GEOUNED.Utils.Options.Classes import Tolerances as tol -from GEOUNED.Utils.Options.Classes import Options as opt -from GEOUNED.CodeVersion import * + import FreeCAD -import copy -import math -import re -import os + +from ..CodeVersion import * +from ..Utils.BasicFunctions_part1 import isOposite, pointsToCoeffs +from ..Utils.Functions import Surfaces_dict +from ..Utils.Options.Classes import Options as opt +from ..Write.Functions import (CellString, PHITSSurface, changeSurfSign, + writePHITSCellDef) + class PHITS_input: def __init__(self,Meta,Surfaces,setting) : diff --git a/src/GEOUNED/Write/SerpentFormat.py b/src/GEOUNED/Write/SerpentFormat.py index 5462e866..d215c7f1 100644 --- a/src/GEOUNED/Write/SerpentFormat.py +++ b/src/GEOUNED/Write/SerpentFormat.py @@ -3,17 +3,14 @@ ################################# from datetime import datetime -from GEOUNED.Utils.Functions import Surfaces_dict -from GEOUNED.Utils.BasicFunctions_part1 import pointsToCoeffs,isOposite -from GEOUNED.Write.Functions import SerpentSurface, changeSurfSign, writeSerpentCellDef,CardLine -from GEOUNED.Utils.Options.Classes import MCNP_numeric_format as nf -from GEOUNED.Utils.Options.Classes import Tolerances as tol -from GEOUNED.Utils.Options.Classes import Options as opt -from GEOUNED.CodeVersion import * import FreeCAD -import copy -import math -import re + +from ..CodeVersion import * +from ..Utils.BasicFunctions_part1 import isOposite, pointsToCoeffs +from ..Utils.Functions import Surfaces_dict +from ..Utils.Options.Classes import Options as opt +from .Functions import SerpentSurface, changeSurfSign, writeSerpentCellDef + class Serpent_input: def __init__(self,Meta,Surfaces,setting) : diff --git a/src/GEOUNED/Write/StringFunctions.py b/src/GEOUNED/Write/StringFunctions.py index 355c5d1b..a1bc1ea3 100644 --- a/src/GEOUNED/Write/StringFunctions.py +++ b/src/GEOUNED/Write/StringFunctions.py @@ -1,6 +1,5 @@ import re - ######################################### # define patterns to be found in string # ######################################### diff --git a/src/GEOUNED/Write/WriteFiles.py b/src/GEOUNED/Write/WriteFiles.py index 615a6ab1..9746065d 100644 --- a/src/GEOUNED/Write/WriteFiles.py +++ b/src/GEOUNED/Write/WriteFiles.py @@ -1,8 +1,9 @@ -import GEOUNED.Write.AdditionalFiles as OutFiles -from GEOUNED.Write.MCNPFormat import MCNP_input -from GEOUNED.Write.OpenMCFormat import OpenMC_input -from GEOUNED.Write.SerpentFormat import Serpent_input -from GEOUNED.Write.PHITSFormat import PHITS_input +from . import AdditionalFiles as OutFiles +from .MCNPFormat import MCNP_input +from .OpenMCFormat import OpenMC_input +from .PHITSFormat import PHITS_input +from .SerpentFormat import Serpent_input + def writeGeometry(UniverseBox,MetaList,Surfaces,code_setting): @@ -63,4 +64,3 @@ def writeGeometry(UniverseBox,MetaList,Surfaces,code_setting): PHITSfile = PHITS_input(MetaList,Surfaces,code_setting) # PHITSfile.setSDEF_PHITS((PHITS_outSphere,PHITS_outBox)) PHITSfile.writePHITS(phitsFilename) - \ No newline at end of file diff --git a/src/GEOUNED/__init__.py b/src/GEOUNED/__init__.py index 0a2b45d8..572ffeca 100644 --- a/src/GEOUNED/__init__.py +++ b/src/GEOUNED/__init__.py @@ -2,26 +2,33 @@ # # We load the STEP and the materials -import sys -import FreeCAD,Part -import configparser -from os import path,mkdir +# this try except attempts to import freecad (lowercase) which is the conda +# package name for FreeCAD (mixed case) upon import the conda package appends +# the sys path for Conda installed FreeCAD, consequently FreeCAD can then be +# found by subsequent import statements through out the code base +try: + import freecad +except: + pass +import configparser from datetime import datetime +from os import mkdir, path +import FreeCAD +import Part -import GEOUNED.LoadFile.LoadSTEP as Load -import GEOUNED.Decompose.Decom_one as Decom -import GEOUNED.Utils.Functions as UF -import GEOUNED.Conversion.CellDefinition as Conv -from GEOUNED.Write.Functions import writeMCNPCellDef -from GEOUNED.Write.WriteFiles import writeGeometry -from GEOUNED.CodeVersion import * -from GEOUNED.Utils.Options.Classes import Tolerances, MCNP_numeric_format, Options -from GEOUNED.Utils.BooleanSolids import buildCTableFromSolids -from GEOUNED.Cuboid.translate import translate - -import GEOUNED.Void.Void as Void +from .CodeVersion import * +from .Conversion import CellDefinition as Conv +from .Cuboid.translate import translate +from .Decompose import Decom_one as Decom +from .LoadFile import LoadSTEP as Load +from .Utils import Functions as UF +from .Utils.BooleanSolids import buildCTableFromSolids +from .Utils.Options.Classes import MCNP_numeric_format, Options, Tolerances +from .Void import Void as Void +from .Write.Functions import writeMCNPCellDef +from .Write.WriteFiles import writeGeometry class GEOUNED() : @@ -76,9 +83,13 @@ def SetOptions(self): elif key == 'stepFile': value = config.get('Files',key).strip() + lst = value.split() if value[0] in ('(','[') and value[-1] in (']',')') : data=value[1:-1].split(',') + data = [ x.strip() for x in data ] self.set(key,data) + elif len(lst) > 1: + self.set(key,lst) else : self.set(key, value) @@ -205,18 +216,20 @@ def Start(self): code_setting = self.__dict__ if code_setting is None: - print('Cannot run the code. Input are missing') - exit() + raise ValueError('Cannot run the code. Input are missing') if code_setting['stepFile'] == '': - print('Cannot run the code. Step file name is missing') - exit() + raise ValueError('Cannot run the code. Step file name is missing') stepfile = code_setting['stepFile'] matfile = code_setting['matFile'] - if not path.isfile(stepfile) : - print('Step file {} not found.\nStop.'.format(stepfile)) - exit() + if isinstance(stepfile,(tuple,list)): + for stp in stepfile: + if not path.isfile(stp) : + raise FileNotFoundError(f'Step file {stp} not found.\nStop.') + else: + if not path.isfile(stepfile) : + raise FileNotFoundError(f'Step file {stepfile} not found.\nStop.') startTime = datetime.now() @@ -251,9 +264,11 @@ def Start(self): if m.IsEnclosure: continue solids.extend(m.Solids) Part.makeCompound(solids).exportStep(code_setting['exportSolids']) - print ('Solids exported in file :{}'.format(code_setting['exportSolids'])) - print ('GEOUNED Finish. No solid translation performed.') - sys.exit() + msg = ( + f'Solids exported in file {code_setting["exportSolids"]}\n' + 'GEOUNED Finish. No solid translation performed.' + ) + raise ValueError(msg) # set up Universe if EnclosureList : diff --git a/testing/test.py b/testing/test.py index 3f167cc1..73524d26 100644 --- a/testing/test.py +++ b/testing/test.py @@ -1,8 +1,9 @@ import sys import os import subprocess +from pathlib import Path -sys.path.append('/home/patrick/recherche/GitRepo/GEOUNED/src') +sys.path.append(str(Path(__file__).parents[1]/'src')) sys.path.append('/usr/lib64/freecad/lib64/') from GEOUNED import GEOUNED diff --git a/tests/cross_sections.xml b/tests/cross_sections.xml new file mode 100644 index 00000000..cf0ac1fc --- /dev/null +++ b/tests/cross_sections.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/test_convert.py b/tests/test_convert.py new file mode 100644 index 00000000..be230e92 --- /dev/null +++ b/tests/test_convert.py @@ -0,0 +1,59 @@ +from pathlib import Path + +import pytest + +import GEOUNED + +path_to_cad = Path("testing/inputSTEP") +step_files = list(path_to_cad.rglob("*.stp")) + list(path_to_cad.rglob("*.step")) + + +@pytest.mark.parametrize("input_step_file", step_files) +def test_conversion(input_step_file): + """Test that step files can be converted to openmc and mcnp files""" + + # sets up an output folder for the results + output_dir = Path("tests_outputs") / input_step_file.with_suffix("") + output_dir.mkdir(parents=True, exist_ok=True) + output_filename_stem = output_dir / input_step_file.stem + + # creates the config file contents + template = ( + "[Files]\n" + "title = 'Input Test'\n" + f"stepFile = {input_step_file.resolve()}\n" + f"geometryName = {output_filename_stem.resolve()}\n" + "outFormat = ('mcnp', 'openMC_XML')\n" + "[Parameters]\n" + "compSolids = False\n" + "volCARD = False\n" + "volSDEF = True\n" + "voidGen = True\n" + "dummyMat = True\n" + "minVoidSize = 100\n" + "cellSummaryFile = False\n" + "cellCommentFile = False\n" + "debug = False\n" + "simplify = no\n" + "[Options]\n" + "forceCylinder = False\n" + "splitTolerance = 0\n" + "newSplitPlane = True\n" + "nPlaneReverse = 0\n" + ) + + with open(output_dir / "config.ini", mode="w") as file: + file.write(template) + + # deletes the output openmc and mcnp output files if it already exists + output_filename_stem.with_suffix(".mcnp").unlink(missing_ok=True) + output_filename_stem.with_suffix(".xml").unlink(missing_ok=True) + + inifile = f"{output_dir/'config.ini'}" + GEO = GEOUNED.GEOUNED(inifile) + GEO.SetOptions() + GEO.outFormat = ("mcnp", "openMC_XML") + GEO.Start() + + assert output_filename_stem.with_suffix(".mcnp").exists() + assert output_filename_stem.with_suffix(".xml").exists() diff --git a/tests/test_transport.py b/tests/test_transport.py new file mode 100644 index 00000000..f9280040 --- /dev/null +++ b/tests/test_transport.py @@ -0,0 +1,92 @@ +""" +The tests check the resulting CSG geometry in particle transport using OpenMC. +The tests assumes that the test_convert script has previously been run and +that xml files exist in the tests_outputs folder. +""" + +import os +import sys +from pathlib import Path + +try: + import freecad # importing conda package if present +except: + pass +import Part +import pytest +from FreeCAD import Import +import openmc +import openmc + +openmc.config["cross_sections"] = Path("tests/cross_sections.xml").resolve() + +path_to_cad = Path("testing/inputSTEP") +step_files = list(path_to_cad.rglob("*.stp")) + list(path_to_cad.rglob("*.step")) + +# these step files all produced failures, most of them lose particles +# they are removed from the tests but an issue has be raised and a minimal +# example has been made in the issue where the fixing of these files can be +# tracked https://github.com/shimwell/GEOUNED/issues/11 +step_files.remove(Path("testing/inputSTEP/large/SCDR.stp")) +step_files.remove(Path("testing/inputSTEP/placa.stp")) +step_files.remove(Path("testing/inputSTEP/placa2.stp")) +# required a few more particles needed but also losing particles +step_files.remove(Path("testing/inputSTEP/DoubleCylinder/placa3.step")) +step_files.remove(Path("testing/inputSTEP/DoubleCylinder/placa.stp")) +# this face2.stp crashes when loading the geometry.xml + + +@pytest.mark.skipif( + sys.platform in ["win32", "darwin"], + reason="OpenMC doesn't install on Windows currently and is not well tested on Mac", +) +@pytest.mark.parametrize("input_step_file", step_files) +def test_transport(input_step_file): + + output_dir = Path("tests_outputs") / input_step_file.with_suffix("") + output_dir.mkdir(parents=True, exist_ok=True) + output_filename_stem = output_dir / input_step_file.stem + openmc_xml_file = output_filename_stem.with_suffix(".xml") + + Import.insert(str(input_step_file), "converted-cad") + result = Part.Shape() + result.read(str(input_step_file)) + + # getting the bounding box of the CAD so we can use it to make a source + # over the whole geometry + bb = result.BoundBox + llx, lly, llz, urx, ury, urz = bb.XMin, bb.YMin, bb.ZMin, bb.XMax, bb.YMax, bb.ZMax + # converting from mm to cm + llx, lly, llz, urx, ury, urz = ( + llx / 10, + lly / 10, + llz / 10, + urx / 10, + ury / 10, + urz / 10, + ) + + source = openmc.IndependentSource() + source.space = openmc.stats.Box( + lower_left=(llx, lly, llz), upper_right=(urx, ury, urz) + ) + source.energy = openmc.stats.Discrete([14e6], [1]) + + materials = openmc.Materials() + geometry = openmc.Geometry.from_xml(openmc_xml_file, materials) + + settings = openmc.Settings() + settings.batches = 10 + settings.max_lost_particles = 1 + # number of particles is increased for local runs as the GitHub action + # runner has two threads and a typical local computer has more. + # Sometimes a lot of particles are needed to find small geometry errors + if os.getenv("GITHUB_ACTIONS"): + settings.particles = 100_000 + else: + settings.particles = 10_000_000 + settings.run_mode = "fixed source" + settings.source = source + model = openmc.Model(geometry, materials, settings) + + model.run() diff --git a/tests/test_volumes.py b/tests/test_volumes.py new file mode 100644 index 00000000..68934c74 --- /dev/null +++ b/tests/test_volumes.py @@ -0,0 +1,109 @@ + +""" +The tests check the resulting volume of the CSG cells using OpenMC. +The tests assumes that the test_convert script has previously been run and +that xml files exist in the tests_outputs folder. +""" +import sys +import math +import os +from pathlib import Path + +try: + import freecad # importing conda package if present +except: + pass +import Part +import pytest +from FreeCAD import Import +import openmc + +with open("cross_sections.xml", "w") as file: + file.write( + """ + + + + + """ + ) +openmc.config['cross_sections'] = Path("cross_sections.xml").resolve() + +path_to_cad = Path("testing/inputSTEP") +step_files = list(path_to_cad.rglob("*.stp")) + list(path_to_cad.rglob("*.step")) +step_files.remove(Path('testing/inputSTEP/Misc/rails.stp')) +# # this checks if the tests are being run a github action runner or not +if os.getenv("GITHUB_ACTIONS"): + # reduced samples as github action runners have 2 threads and more samples + # is not needed for the smaller models tested. This allows the CI to + # quickly test the smaller models + samples=4_000_000 + rel_tol=0.05 +else: + # samples for local run can be larger as threads is likely to be larger + samples=400_000_000 + # acceptable tolerance can also be smaller + rel_tol=0.01 + + +@pytest.mark.skipif( + sys.platform in ["win32", "darwin"], + reason="OpenMC doesn't install on Windows currently and is not well tested on Mac" +) +@pytest.mark.parametrize( + "input_step_file", step_files +) +def test_volumes(input_step_file): + + output_dir = Path("tests_outputs") / input_step_file.with_suffix("") + output_dir.mkdir(parents=True, exist_ok=True) + output_filename_stem = output_dir / input_step_file.stem + openmc_xml_file = output_filename_stem.with_suffix(".xml") + + Import.insert(str(input_step_file), "converted-cad") + result = Part.Shape() + result.read(str(input_step_file)) + + materials = openmc.Materials() + geometry = openmc.Geometry.from_xml(openmc_xml_file, materials) + cells = geometry.get_all_cells() + + # some of the csg cells are void space around the CAD, so we just check + # that there are at least as many cells as there could be more cells in the + # CSG than the CAD + assert len(result.Solids) <= len(cells.keys()) + + all_cell_vol_calc = [] + for solid, (cell_id, cell) in zip(result.Solids, cells.items()): + + bb = solid.BoundBox + llx, lly, llz, urx, ury, urz = bb.XMin, bb.YMin, bb.ZMin, bb.XMax, bb.YMax, bb.ZMax + llx, lly, llz, urx, ury, urz = llx/10, lly/10, llz/10, urx/10, ury/10, urz/10 + + cell_vol_calc = openmc.VolumeCalculation( + domains=[cell], + samples=samples, + lower_left=(llx, lly, llz), + upper_right=(urx, ury, urz), + ) + # could add a trigger to ends tests early once they have converged + # cell_vol_calc.set_trigger(0.05, 'rel_err') + + all_cell_vol_calc.append(cell_vol_calc) + + settings = openmc.Settings() + settings.volume_calculations = all_cell_vol_calc + settings.run_mode = "volume" + model = openmc.Model(geometry, materials, settings) + + model.export_to_model_xml(path=openmc_xml_file.with_name('model.xml')) + openmc.run(cwd=output_dir) + + for solid, (cell_id, cell) in zip(result.Solids, cells.items()): + + vol_calc_file = openmc_xml_file.with_name(f"volume_{cell_id}.h5") + cell_vol_calc_results = openmc.VolumeCalculation.from_hdf5(vol_calc_file) + volume_of_csg_cell = cell_vol_calc_results.volumes[cell_id].nominal_value + # converts from mm3 in cad to cm3 in csg + volume_of_cad_cell = solid.Volume * 0.001 + assert math.isclose(volume_of_cad_cell, volume_of_csg_cell, rel_tol=rel_tol)