diff --git a/.github/workflows/auto_format_pep8.yml b/.github/workflows/auto_format_pep8.yml
index a329f2ee5..88ee8807c 100644
--- a/.github/workflows/auto_format_pep8.yml
+++ b/.github/workflows/auto_format_pep8.yml
@@ -1,4 +1,4 @@
-name: black
+name: auto_format_pep8
on:
push:
@@ -23,7 +23,7 @@ jobs:
- name: Install black
run: |
python -m pip install --upgrade pip
- pip install black==22.1.0
+ pip install black==22.3.0
- name: Run black
run: |
black .
diff --git a/.github/workflows/auto_lint.yml b/.github/workflows/auto_lint.yml
index 41ce6764e..63a74f7d1 100644
--- a/.github/workflows/auto_lint.yml
+++ b/.github/workflows/auto_lint.yml
@@ -1,4 +1,4 @@
-name: black
+name: auto_lint
on:
push:
diff --git a/.github/workflows/test_demos.yml b/.github/workflows/test_demos.yml
index dfe7e15ad..efa86952b 100644
--- a/.github/workflows/test_demos.yml
+++ b/.github/workflows/test_demos.yml
@@ -7,6 +7,13 @@ on:
branches:
- main
- develop
+ paths:
+ - "**.py"
+ - "**.ipynb"
+ - "**.yml"
+ - "**.cfg"
+ - "**.toml"
+ - "**.sh"
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
diff --git a/README.md b/README.md
index b9ce3e659..43949ce60 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,8 @@
[![codecov](https://codecov.io/gh/fusion-energy/paramak/branch/main/graph/badge.svg)](https://codecov.io/gh/fusion-energy/paramak)
-[![Code Grade](https://www.code-inspector.com/project/25342/score/svg)](https://frontend.code-inspector.com/public/project/25342/paramak/dashboard)
-[![Code Grade](https://www.code-inspector.com/project/25342/status/svg)](https://frontend.code-inspector.com/public/project/25342/paramak/dashboard)
+[![Code Grade](https://api.codiga.io/project/25342/score/svg)](https://app.codiga.io/public/project/25342/paramak/dashboard)
+[![Code Grade](https://api.codiga.io/project/25342/status/svg)](https://app.codiga.io/public/project/25342/paramak/dashboard)
[![Documentation Status](https://readthedocs.org/projects/paramak/badge/?version=main)](https://paramak.readthedocs.io/en/main/?badge=main)
@@ -17,16 +17,19 @@
[![Upload Python Package](https://github.com/fusion-energy/paramak/actions/workflows/python-publish.yml/badge.svg)](https://github.com/fusion-energy/paramak/actions/workflows/python-publish.yml)
[![PyPI](https://img.shields.io/pypi/v/paramak?color=brightgreen&label=pypi&logo=grebrightgreenen&logoColor=green)](https://pypi.org/project/paramak/)
+[![anaconda-publish](https://github.com/fusion-energy/paramak/actions/workflows/anaconda-publish.yml/badge.svg)](https://github.com/fusion-energy/paramak/actions/workflows/anaconda-publish.yml)
+[![anaconda.org](https://anaconda.org/fusion-energy/paramak/badges/version.svg)](https://anaconda.org/fusion-energy/paramak)
+
[![docker-publish-release](https://github.com/fusion-energy/paramak/actions/workflows/docker_publish.yml/badge.svg)](https://github.com/fusion-energy/paramak/actions/workflows/docker_publish.yml)
[![DOI](https://zenodo.org/badge/269635577.svg)](https://zenodo.org/badge/latestdoi/269635577)
# Paramak
-The Paramak python package allows rapid production of 3D CAD models of fusion
-reactors. The purpose of the Paramak is to provide geometry for parametric
-studies. The paramak can create geometry in standard CAD formats such as STP,
-STL and Brep.
+Paramak python package allows rapid production of 3D CAD models and neutronics
+models of fusion reactors. The purpose of Paramak is to provide geometry for
+parametric studies. Paramak can create geometry in standard CAD formats such as
+STP, STL, BRep, HTML and DAGMC h5m.
:point_right: [Documentation](https://paramak.readthedocs.io)
diff --git a/conda/meta.yaml b/conda/meta.yaml
index 45d7c4b04..35887f271 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -19,10 +19,10 @@ requirements:
- python {{ python }}
- cadquery {{ cadquery }}
- mpmath
- - plasmaboundaries
+ - plasmaboundaries >=0.1.8
- plotly
- - brep_part_finder # [not win]
- - brep_to_h5m # [not win]
+ - brep_part_finder >=0.4.1 # [not win]
+ - brep_to_h5m >=0.3.1 # [not win]
# - jupyter-cadquery not available on conda
test:
diff --git a/docs/source/API-Reference.rst b/docs/source/API-Reference.rst
index dedbf4cfa..aa38f3b5d 100644
--- a/docs/source/API-Reference.rst
+++ b/docs/source/API-Reference.rst
@@ -1516,6 +1516,43 @@ CapsuleVacuumVessel()
:show-inheritance:
+DishedVacuumVessel()
+^^^^^^^^^^^^^^^^^^^^
+.. cadquery::
+ :select: cadquery_object
+ :gridsize: 0
+
+ import paramak
+ my_component = paramak.DishedVacuumVessel(rotation_angle=180)
+
+ cadquery_object = my_component.solid
+
+.. image:: https://user-images.githubusercontent.com/8583900/160503281-3aabf145-22b0-4953-bc4f-3f75a6696b5e.png
+
+.. automodule:: paramak.parametric_components.dished_vacuum_vessel
+ :members:
+ :show-inheritance:
+
+ConstantThicknessDome()
+^^^^^^^^^^^^^^^^^^^^^^^
+.. cadquery::
+ :select: cadquery_object
+ :gridsize: 0
+
+ import paramak
+ my_component = paramak.ConstantThicknessDome(rotation_angle=180)
+
+ cadquery_object = my_component.solid
+
+.. image:: https://user-images.githubusercontent.com/8583900/160503286-71a6d771-1d47-476e-a0cf-85fb94c9389c.png
+
+.. automodule:: paramak.parametric_components.constant_thickness_dome
+ :members:
+ :show-inheritance:
+
+
+
+
Other components
----------------
diff --git a/examples/example_parametric_reactors/render_of_random_reactor_wireframe_with_text_gif.py b/examples/example_parametric_reactors/render_of_random_reactor_wireframe_with_text_gif.py
index 4640e028c..92c70f2bb 100644
--- a/examples/example_parametric_reactors/render_of_random_reactor_wireframe_with_text_gif.py
+++ b/examples/example_parametric_reactors/render_of_random_reactor_wireframe_with_text_gif.py
@@ -3,11 +3,9 @@
# animation. Two animations are made, of of a 3D render and one of a wireframe
# line drawing.
-import math
import os
# to run this example you will need all of the following packages installed
-import matplotlib.pyplot as plt
import numpy as np
import paramak
diff --git a/paramak/__init__.py b/paramak/__init__.py
index 7f8a70a5b..8c222acda 100644
--- a/paramak/__init__.py
+++ b/paramak/__init__.py
@@ -13,7 +13,18 @@
from .shape import Shape
from .reactor import Reactor
-from .utils import rotate, extend, distance_between_two_points, diff_between_angles
+from .utils import (
+ rotate,
+ extend,
+ distance_between_two_points,
+ diff_between_angles,
+ find_center_point_of_circle,
+ angle_between_two_points_on_circle,
+ find_radius_of_circle,
+ export_solids_to_brep,
+ export_solids_to_dagmc_h5m,
+ get_center_of_bounding_box,
+)
from .utils import EdgeLengthSelector, FaceAreaSelector
from .parametric_shapes.extruded_mixed_shape import ExtrudeMixedShape
@@ -113,7 +124,10 @@
ToroidalFieldCoilRectangleRoundCorners,
)
+from .parametric_components.constant_thickness_dome import ConstantThicknessDome
from .parametric_components.vacuum_vessel import VacuumVessel
+
+from .parametric_components.dished_vacuum_vessel import DishedVacuumVessel
from .parametric_components.vacuum_vessel_inner_leg import VacuumVesselInnerLeg
from .parametric_components.capsule_vacuum_vessel import CapsuleVacuumVessel
from .parametric_components.hollow_cube import HollowCube
diff --git a/paramak/parametric_components/capsule_vacuum_vessel.py b/paramak/parametric_components/capsule_vacuum_vessel.py
index bc77bfa2a..26e61432c 100644
--- a/paramak/parametric_components/capsule_vacuum_vessel.py
+++ b/paramak/parametric_components/capsule_vacuum_vessel.py
@@ -6,7 +6,7 @@
class CapsuleVacuumVessel(RotateMixedShape):
"""A cylindrical vessel volume with constant thickness that has addition
- spherical edges.
+ hemispherical head.
Arguments:
outer_start_point: the x,z coordinates of the outer bottom of the
diff --git a/paramak/parametric_components/constant_thickness_dome.py b/paramak/parametric_components/constant_thickness_dome.py
new file mode 100644
index 000000000..45ee92e32
--- /dev/null
+++ b/paramak/parametric_components/constant_thickness_dome.py
@@ -0,0 +1,210 @@
+import math
+from paramak import RotateMixedShape, RotateStraightShape, Shape, CuttingWedge
+import cadquery as cq
+
+
+class ConstantThicknessDome(RotateMixedShape):
+ """A cylindrical vessel volume with constant thickness with a simple dished
+ head. This style of tank head has no knuckle radius or straight flange. The
+ dished shape is made from a chord of a circle.
+
+ Arguments:
+ thickness: the radial thickness of the dome.
+ chord_center_height: the vertical position of the chord center
+ chord_width: the width of the chord base
+ chord_height: the height of the chord which is also distance between
+ the chord_center_height and the inner surface of the dome
+ upper_or_lower: Curves the dish with a positive or negative direction
+ to allow the upper section or lower section of vacuum vessel
+ domes to be made.
+ name: the name of the shape, used in the graph legend and as a
+ filename prefix when exporting.
+ """
+
+ def __init__(
+ self,
+ thickness: float = 10,
+ chord_center_height: float = 0,
+ chord_width: float = 100,
+ chord_height: float = 20,
+ upper_or_lower: str = "upper",
+ name: str = "constant_thickness_dome",
+ **kwargs,
+ ):
+
+ self.thickness = thickness
+ self.chord_center_height = chord_center_height
+ self.chord_width = chord_width
+ self.chord_height = chord_height
+ self.upper_or_lower = upper_or_lower
+ self.name = name
+
+ super().__init__(name=name, **kwargs)
+
+ @property
+ def chord_width(self):
+ return self._chord_width
+
+ @chord_width.setter
+ def chord_width(self, value):
+ if not isinstance(value, (float, int)):
+ raise ValueError("ConstantThicknessDome.chord_width must be a number. Not", value)
+ if value <= 0:
+ msg = f"ConstantThicknessDome.chord_width must be a positive number above 0. Not {value}"
+ raise ValueError(msg)
+ self._chord_width = value
+
+ @property
+ def chord_height(self):
+ return self._chord_height
+
+ @chord_height.setter
+ def chord_height(self, value):
+ if not isinstance(value, (float, int)):
+ raise ValueError("ConstantThicknessDome.chord_height must be a number. Not", value)
+ if value <= 0:
+ msg = f"ConstantThicknessDome.chord_height must be a positive number above 0. Not {value}"
+ raise ValueError(msg)
+ self._chord_height = value
+
+ @property
+ def thickness(self):
+ return self._thickness
+
+ @thickness.setter
+ def thickness(self, value):
+ if not isinstance(value, (float, int)):
+ msg = f"VacuumVessel.thickness must be a number. Not {value}"
+ raise ValueError(msg)
+ if value <= 0:
+ msg = f"VacuumVessel.thickness must be a positive number above 0. Not {value}"
+ raise ValueError(msg)
+ self._thickness = value
+
+ def find_points(self):
+ """
+ Finds the XZ points joined by straight and circle connections that
+ describe the 2D profile of the vessel shape.
+ """
+
+ # Note these points are not used in the normal way when constructing
+ # the solid
+ #
+ # 6 -
+ # | -
+ # 7 - -
+ # - -
+ # - 3
+ # - |
+ # cc 1 -- 2
+ # chord center
+ #
+ #
+ # cp
+ # center point
+ #
+ #
+ #
+ #
+ # cc 1 -- 2
+ # - |
+ # - 3
+ # - -
+ # 7 - -
+ # | -
+ # 6 -
+ # far side
+
+ if self.chord_height * 2 >= self.chord_width:
+ msg = "ConstantThicknessDome requires that the self.chord_width is at least 2 times as large as the chord height"
+ raise ValueError(msg)
+
+ radius_of_sphere = ((math.pow(self.chord_width, 2)) + (4.0 * math.pow(self.chord_height, 2))) / (
+ 8 * self.chord_height
+ )
+
+ # TODO set to 0 for now, add ability to shift the center of the chord left and right
+ chord_center = (0, self.chord_center_height)
+
+ point_1 = (chord_center[0] + (self.chord_width / 2), chord_center[1], "straight")
+
+ if self.upper_or_lower == "upper":
+ center_point = (chord_center[0], chord_center[1] + self.chord_height - radius_of_sphere)
+ inner_tri_angle = math.atan((center_point[1] - chord_center[1]) / (self.chord_width / 2))
+ outer_tri_adj = math.cos(inner_tri_angle) * self.thickness
+ point_2 = (point_1[0] + self.thickness, point_1[1], "straight")
+ outer_tri_opp = math.sqrt(math.pow(self.thickness, 2) - math.pow(outer_tri_adj, 2))
+ point_7 = (chord_center[0], chord_center[1] + radius_of_sphere, "straight")
+ point_6 = (chord_center[0], chord_center[1] + radius_of_sphere + self.thickness, "straight")
+ self.far_side = (center_point[0], center_point[1] - (radius_of_sphere + self.thickness))
+ point_3 = (point_2[0], point_2[1] + outer_tri_opp, "straight")
+ elif self.upper_or_lower == "lower":
+ center_point = (chord_center[0], chord_center[1] - self.chord_height + radius_of_sphere)
+ inner_tri_angle = math.atan((center_point[1] - chord_center[1]) / (self.chord_width / 2))
+ outer_tri_adj = math.cos(inner_tri_angle) * self.thickness
+ point_2 = (point_1[0] + self.thickness, point_1[1], "straight")
+ outer_tri_opp = math.sqrt(math.pow(self.thickness, 2) - math.pow(outer_tri_adj, 2))
+ point_7 = (chord_center[0], chord_center[1] - radius_of_sphere, "straight")
+ point_6 = (chord_center[0], chord_center[1] - (radius_of_sphere + self.thickness), "straight")
+ self.far_side = (center_point[0], center_point[1] + radius_of_sphere + self.thickness)
+ point_3 = (point_2[0], point_2[1] - outer_tri_opp, "straight")
+ else:
+ msg = f'upper_or_lower should be either "upper" or "lower". Not {self.upper_or_lower}'
+ raise ValueError(msg)
+
+ self.points = [point_1, point_2, point_3, point_6, point_7]
+
+ def create_solid(self):
+ """Creates a rotated 3d solid using points with circular edges.
+
+ Returns:
+ A CadQuery solid: A 3D solid volume
+ """
+
+ radius_of_sphere = ((math.pow(self.chord_width, 2)) + (4.0 * math.pow(self.chord_height, 2))) / (
+ 8 * self.chord_height
+ )
+
+ # TODO set to 0 for now, add ability to shift the center of the chord left and right
+ chord_center = (0, self.chord_center_height)
+
+ if self.upper_or_lower == "upper":
+ center_point = (chord_center[0], chord_center[1] + self.chord_height - radius_of_sphere)
+ far_side = (center_point[0], center_point[1] - (radius_of_sphere + self.thickness))
+ elif self.upper_or_lower == "lower":
+ center_point = (chord_center[0], chord_center[1] - self.chord_height + radius_of_sphere)
+ far_side = (center_point[0], center_point[1] + radius_of_sphere + self.thickness)
+ else:
+ raise ValueError("self.upper_or_lower")
+
+ big_sphere = (
+ cq.Workplane(self.workplane)
+ .moveTo(center_point[0], center_point[1])
+ .sphere(radius_of_sphere + self.thickness)
+ )
+ small_sphere = cq.Workplane(self.workplane).moveTo(center_point[0], center_point[1]).sphere(radius_of_sphere)
+
+ outer_cylinder_cutter = RotateStraightShape(
+ workplane=self.workplane,
+ points=(
+ (chord_center[0], chord_center[1]), # cc
+ (self.points[1][0], self.points[1][1]), # point 2
+ (self.points[2][0], self.points[2][1]), # point 3
+ (self.points[2][0] + radius_of_sphere, self.points[2][1]), # point 3 wider
+ (self.points[2][0] + radius_of_sphere, far_side[1]),
+ far_side,
+ ),
+ rotation_angle=360,
+ )
+
+ cap = Shape()
+ cap.solid = big_sphere.cut(small_sphere)
+
+ height = 2 * (radius_of_sphere + abs(center_point[1]) + self.thickness)
+ radius = 2 * (radius_of_sphere + abs(center_point[0]) + self.thickness)
+ cutter = CuttingWedge(height=height, radius=radius, rotation_angle=self.rotation_angle)
+
+ cap.solid = cap.solid.intersect(cutter.solid)
+ cap.solid = cap.solid.cut(outer_cylinder_cutter.solid)
+
+ self.solid = cap.solid
diff --git a/paramak/parametric_components/cutting_wedge_fs.py b/paramak/parametric_components/cutting_wedge_fs.py
index f732e8850..18f592e45 100644
--- a/paramak/parametric_components/cutting_wedge_fs.py
+++ b/paramak/parametric_components/cutting_wedge_fs.py
@@ -94,7 +94,10 @@ def azimuth_placement_angle(self, value):
def find_radius_height(self):
shape = self.shape
if shape.rotation_angle == 360:
- msg = "cutting_wedge cannot be created, rotation_angle must be " "less than 360 degrees"
+ msg = (
+ "cutting_wedge cannot be created, the shapes rotation_angle "
+ f"must be less than 360 degrees. shape.rotation_angle is {shape.rotation_angle}"
+ )
raise ValueError(msg)
shape_points = shape.points
if hasattr(shape, "radius") and len(shape_points) == 1:
diff --git a/paramak/parametric_components/dished_vacuum_vessel.py b/paramak/parametric_components/dished_vacuum_vessel.py
new file mode 100644
index 000000000..20f53800d
--- /dev/null
+++ b/paramak/parametric_components/dished_vacuum_vessel.py
@@ -0,0 +1,121 @@
+from paramak import RotateMixedShape, CenterColumnShieldCylinder, ConstantThicknessDome
+
+
+class DishedVacuumVessel(RotateMixedShape):
+ """A cylindrical vessel volume with constant thickness with a simple dished
+ head. This style of tank head has no knuckle radius or straight flange.
+
+ Arguments:
+ radius: the radius from which the centres of the vessel meets the outer
+ circumference.
+ center_point: the x,z coordinates of the center of the vessel
+ dish_height: the height of the dish section. This is also the chord
+ heigh of the circle used to make the dish.
+ cylinder_height: the height of the cylindrical section of the vacuum
+ vessel.
+ thickness: the radial thickness of the vessel in cm.
+ """
+
+ def __init__(
+ self,
+ radius: float = 300,
+ center_point: float = 0,
+ dish_height: float = 50,
+ cylinder_height: float = 400,
+ thickness: float = 15,
+ name: str = "dished_vacuum_vessel",
+ **kwargs,
+ ):
+ self.radius = radius
+ self.center_point = center_point
+ self.dish_height = dish_height
+ self.cylinder_height = cylinder_height
+ self.thickness = thickness
+ self.name = name
+
+ super().__init__(name=name, **kwargs)
+
+ @property
+ def radius(self):
+ return self._radius
+
+ @radius.setter
+ def radius(self, value):
+ if not isinstance(value, (float, int)):
+ raise ValueError("VacuumVessel.radius must be a number. Not", value)
+ if value <= 0:
+ msg = "VacuumVessel.radius must be a positive number above 0. " f"Not {value}"
+ raise ValueError(msg)
+ self._radius = value
+
+ @property
+ def thickness(self):
+ return self._thickness
+
+ @thickness.setter
+ def thickness(self, value):
+ if not isinstance(value, (float, int)):
+ msg = f"VacuumVessel.thickness must be a number. Not {value}"
+ raise ValueError(msg)
+ if value <= 0:
+ msg = f"VacuumVessel.thickness must be a positive number above 0. Not {value}"
+ raise ValueError(msg)
+ self._thickness = value
+
+ def create_solid(self):
+ """Creates a rotated 3d solid using points with circular edges.
+
+ Returns:
+ A CadQuery solid: A 3D solid volume
+ """
+
+ #
+ # - -
+ # -
+ # - - -
+ # - -
+ # - -
+ # - |
+ # | |
+ # | |
+ # | |
+ # c,p | |
+ # | |
+ # | |
+ # | |
+ # - |
+ # - -
+ # - -
+ # - - -
+ # -
+ # - -
+ #
+
+ cylinder_section = CenterColumnShieldCylinder(
+ height=self.cylinder_height,
+ inner_radius=self.radius - self.thickness,
+ outer_radius=self.radius,
+ center_height=self.center_point,
+ rotation_angle=self.rotation_angle,
+ )
+
+ upper_dome_section = ConstantThicknessDome(
+ thickness=self.thickness,
+ chord_center_height=self.center_point + 0.5 * self.cylinder_height,
+ chord_width=(self.radius - self.thickness) * 2,
+ chord_height=self.dish_height,
+ upper_or_lower="upper",
+ rotation_angle=self.rotation_angle,
+ )
+
+ lower_dome_section = ConstantThicknessDome(
+ thickness=self.thickness,
+ chord_center_height=self.center_point - 0.5 * self.cylinder_height,
+ chord_width=(self.radius - self.thickness) * 2,
+ chord_height=self.dish_height,
+ upper_or_lower="lower",
+ rotation_angle=self.rotation_angle,
+ )
+
+ upper_dome_section.solid = upper_dome_section.solid.union(cylinder_section.solid)
+ self.solid = lower_dome_section.solid.union(upper_dome_section.solid)
diff --git a/paramak/parametric_components/extrude_hollow_rectangle.py b/paramak/parametric_components/extrude_hollow_rectangle.py
index 24611800e..3f84b3f0b 100644
--- a/paramak/parametric_components/extrude_hollow_rectangle.py
+++ b/paramak/parametric_components/extrude_hollow_rectangle.py
@@ -8,9 +8,10 @@ class ExtrudeHollowRectangle(ExtrudeStraightShape):
"""Creates a rectangular with a hollow section extrusion.
Args:
- height: the vertical (z axis) height of the rectangle (cm).
- width: the horizontal (x axis) width of the rectangle (cm).
- casing_thickness: the thickness of the casing (cm).
+ height: the height of the internal hollow section.
+ width: the width of the internal hollow section.
+ distance: the depth of the internal hollow section.
+ casing_thickness: the thickness of the casing around the hollow section.
center_point: the center of the rectangle (x,z) values (cm).
name: defaults to "extrude_rectangle".
"""
@@ -82,7 +83,7 @@ def find_points(self):
# 9-------------6
# | 4 -------5,1|
# | | | |
- # | | (0,0) | |
+ # | | cp | |
# | | | |
# | 3 ------- 2 |
# 8-------------7
diff --git a/paramak/parametric_components/hollow_cube.py b/paramak/parametric_components/hollow_cube.py
index 02f5d1e12..caa727127 100644
--- a/paramak/parametric_components/hollow_cube.py
+++ b/paramak/parametric_components/hollow_cube.py
@@ -1,5 +1,6 @@
-import cadquery as cq
+from typing import Tuple
+import cadquery as cq
from paramak import Shape
@@ -8,15 +9,24 @@ class HollowCube(Shape):
Graveyard.
Arguments:
- length (float): The length to use for the height, width, depth of the
+ length: The length to use for the height, width, depth of the
inner dimensions of the cube.
- thickness (float, optional): thickness of the vessel. Defaults to 10.0.
+ thickness: thickness of the vessel.
+ center_coordinate: the location the center of the cube.
"""
- def __init__(self, length, thickness=10.0, **kwargs):
+ def __init__(
+ self,
+ length: float,
+ thickness: float = 10.0,
+ center_coordinate: Tuple[float, float, float] = (0.0, 0.0, 0.0),
+ name="hollow_cube",
+ **kwargs
+ ):
+ super().__init__(name=name, **kwargs)
self.length = length
self.thickness = thickness
- super().__init__(**kwargs)
+ self.center_coordinate = center_coordinate
@property
def thickness(self):
@@ -37,13 +47,17 @@ def length(self, value):
def create_solid(self):
# creates a small box that surrounds the geometry
- inner_box = cq.Workplane("front").box(self.length, self.length, self.length)
+ inner_box = cq.Workplane("front").box(self.length, self.length, self.length).translate(self.center_coordinate)
# creates a large box that surrounds the smaller box
- outer_box = cq.Workplane("front").box(
- self.length + self.thickness,
- self.length + self.thickness,
- self.length + self.thickness,
+ outer_box = (
+ cq.Workplane("front")
+ .box(
+ self.length + self.thickness,
+ self.length + self.thickness,
+ self.length + self.thickness,
+ )
+ .translate(self.center_coordinate)
)
# subtracts the two boxes to leave a hollow box
diff --git a/paramak/parametric_reactors/flf_system_code_reactor.py b/paramak/parametric_reactors/flf_system_code_reactor.py
index 54e146910..f6158508a 100644
--- a/paramak/parametric_reactors/flf_system_code_reactor.py
+++ b/paramak/parametric_reactors/flf_system_code_reactor.py
@@ -77,19 +77,16 @@ def create_solids(self):
inner_wall = self.inner_blanket_radius + self.blanket_thickness + self.blanket_vv_gap
lower_vv = paramak.RotateStraightShape(
points=[
+ (inner_wall, 0),
(
inner_wall,
- (-self.blanket_height / 2.0) - self.lower_blanket_thickness,
- ),
- (
- inner_wall,
- (-self.blanket_height / 2.0) - (self.lower_blanket_thickness + self.lower_vv_thickness),
+ self.lower_vv_thickness,
),
(
0,
- (-self.blanket_height / 2.0) - (self.lower_blanket_thickness + self.lower_vv_thickness),
+ self.lower_vv_thickness,
),
- (0, (-self.blanket_height / 2.0) - self.lower_blanket_thickness),
+ (0, 0),
],
rotation_angle=self.rotation_angle,
color=(0.5, 0.5, 0.5),
@@ -98,13 +95,10 @@ def create_solids(self):
lower_blanket = paramak.RotateStraightShape(
points=[
- (inner_wall, -self.blanket_height / 2.0),
- (
- inner_wall,
- (-self.blanket_height / 2.0) - self.lower_blanket_thickness,
- ),
- (0, (-self.blanket_height / 2.0) - self.lower_blanket_thickness),
- (0, -self.blanket_height / 2.0),
+ (inner_wall, self.lower_vv_thickness),
+ (inner_wall, self.lower_vv_thickness + self.lower_blanket_thickness),
+ (0, self.lower_vv_thickness + self.lower_blanket_thickness),
+ (0, self.lower_vv_thickness),
],
rotation_angle=self.rotation_angle,
color=(0.0, 1.0, 0.498),
@@ -113,6 +107,7 @@ def create_solids(self):
blanket = paramak.CenterColumnShieldCylinder(
height=self.blanket_height,
+ center_height=self.lower_vv_thickness + self.lower_blanket_thickness + 0.5 * self.blanket_height,
inner_radius=self.inner_blanket_radius,
outer_radius=self.blanket_thickness + self.inner_blanket_radius,
rotation_angle=self.rotation_angle,
@@ -121,54 +116,88 @@ def create_solids(self):
name="blanket",
)
- upper_blanket = paramak.RotateStraightShape(
+ upper_vv = paramak.RotateStraightShape(
points=[
- (inner_wall, (self.blanket_height / 2.0) + self.upper_vv_thickness),
+ (inner_wall, self.lower_vv_thickness + self.lower_blanket_thickness + self.blanket_height),
(
inner_wall,
- (self.blanket_height / 2.0) + self.upper_vv_thickness + self.upper_blanket_thickness,
+ self.lower_vv_thickness
+ + self.lower_blanket_thickness
+ + self.blanket_height
+ + self.upper_vv_thickness,
),
(
0,
- (self.blanket_height / 2.0) + self.upper_vv_thickness + self.upper_blanket_thickness,
+ self.lower_vv_thickness
+ + self.lower_blanket_thickness
+ + self.blanket_height
+ + self.upper_vv_thickness,
),
- (0, (self.blanket_height / 2.0) + self.upper_vv_thickness),
- ],
- rotation_angle=self.rotation_angle,
- color=(0.0, 1.0, 0.498),
- name="upper_blanket",
- )
-
- upper_vv = paramak.RotateStraightShape(
- points=[
- (inner_wall, self.blanket_height / 2.0),
- (inner_wall, (self.blanket_height / 2.0) + self.upper_vv_thickness),
- (0, (self.blanket_height / 2.0) + self.upper_vv_thickness),
- (0, self.blanket_height / 2.0),
+ (0, self.lower_vv_thickness + self.lower_blanket_thickness + self.blanket_height),
],
rotation_angle=self.rotation_angle,
color=(0.5, 0.5, 0.5),
name="upper_vacuum_vessel",
)
- vac_ves = paramak.RotateStraightShape(
+ upper_blanket = paramak.RotateStraightShape(
points=[
(
- inner_wall + self.vv_thickness,
- (self.blanket_height / 2.0) + self.upper_vv_thickness + self.upper_blanket_thickness,
+ inner_wall,
+ self.lower_vv_thickness
+ + self.lower_blanket_thickness
+ + self.blanket_height
+ + self.upper_vv_thickness,
),
(
inner_wall,
- (self.blanket_height / 2.0) + self.upper_vv_thickness + self.upper_blanket_thickness,
+ self.lower_vv_thickness
+ + self.lower_blanket_thickness
+ + self.blanket_height
+ + self.upper_vv_thickness
+ + self.upper_blanket_thickness,
+ ),
+ (
+ 0,
+ self.lower_vv_thickness
+ + self.lower_blanket_thickness
+ + self.blanket_height
+ + self.upper_vv_thickness
+ + self.upper_blanket_thickness,
+ ),
+ (
+ 0,
+ self.lower_vv_thickness
+ + self.lower_blanket_thickness
+ + self.blanket_height
+ + self.upper_vv_thickness,
),
+ ],
+ rotation_angle=self.rotation_angle,
+ color=(0.0, 1.0, 0.498),
+ name="upper_blanket",
+ )
+
+ vac_ves = paramak.RotateStraightShape(
+ points=[
+ (inner_wall, 0),
(
inner_wall,
- -(self.blanket_height / 2.0) - self.lower_blanket_thickness - self.lower_vv_thickness,
+ self.lower_vv_thickness
+ + self.lower_blanket_thickness
+ + self.blanket_height
+ + self.upper_vv_thickness
+ + self.upper_blanket_thickness,
),
(
inner_wall + self.vv_thickness,
- -(self.blanket_height / 2.0) - self.lower_blanket_thickness - self.lower_vv_thickness,
+ self.lower_vv_thickness
+ + self.lower_blanket_thickness
+ + self.blanket_height
+ + self.upper_vv_thickness
+ + self.upper_blanket_thickness,
),
+ (inner_wall + self.vv_thickness, 0),
],
rotation_angle=self.rotation_angle,
color=(0.5, 0.5, 0.5),
diff --git a/paramak/reactor.py b/paramak/reactor.py
index 50e90876b..e805cfa1a 100644
--- a/paramak/reactor.py
+++ b/paramak/reactor.py
@@ -1,6 +1,5 @@
-import os
-import tempfile
from collections.abc import Iterable
+from logging import warning
from pathlib import Path
from typing import List, Optional, Tuple, Union
@@ -9,7 +8,15 @@
from cadquery import exporters
import paramak
-from paramak.utils import _replace, get_hash
+from paramak.utils import (
+ _replace,
+ get_hash,
+ get_bounding_box,
+ get_largest_dimension,
+ export_solids_to_brep,
+ export_solids_to_dagmc_h5m,
+ get_center_of_bounding_box,
+)
class Reactor:
@@ -20,37 +27,17 @@ class Reactor:
Args:
shapes_and_components: list of paramak.Shape objects
- graveyard_size: The dimension of cube shaped the graveyard region used
- by DAGMC. This attribute is used preferentially over
- graveyard_offset.
- graveyard_offset: The distance between the graveyard and the largest
- shape. If graveyard_size is set the this is ignored.
- largest_shapes: Identifying the shape(s) with the largest size in each
- dimension (x,y,z) can speed up the production of the graveyard.
- Defaults to None which finds the largest shapes by looping through
- all the shapes and creating bounding boxes. This can be slow and
- that is why the user is able to provide a subsection of shapes to
- use when calculating the graveyard dimensions.
"""
def __init__(
self,
shapes_and_components: List[paramak.Shape] = [],
- graveyard_size: float = 20_000.0,
- graveyard_offset: Optional[float] = None,
- largest_shapes: Optional[List[paramak.Shape]] = None,
):
self.shapes_and_components = shapes_and_components
- self.graveyard_offset = graveyard_offset
- self.graveyard_size = graveyard_size
- self.largest_shapes = largest_shapes
self.input_variable_names: List[str] = [
# 'shapes_and_components', commented out to avoid calculating solids
- "graveyard_size",
- "graveyard_offset",
- "largest_shapes",
]
self.stp_filenames: List[str] = []
@@ -99,16 +86,9 @@ def graveyard_offset(self, value):
def largest_dimension(self):
"""Calculates a bounding box for the Reactor and returns the largest
absolute value of the largest dimension of the bounding box"""
- largest_dimension = 0
- if self.largest_shapes is None:
- shapes_to_bound = self.shapes_and_components
- else:
- shapes_to_bound = self.largest_shapes
+ largest_dimension = get_largest_dimension(self.solid)
- for component in shapes_to_bound:
- largest_dimension = max(largest_dimension, component.largest_dimension)
- # self._largest_dimension = largest_dimension
return largest_dimension
@largest_dimension.setter
@@ -116,14 +96,16 @@ def largest_dimension(self, value):
self._largest_dimension = value
@property
- def largest_shapes(self):
- return self._largest_shapes
+ def bounding_box(self):
+ """Calculates a bounding box for the Shape and returns the coordinates of
+ the corners lower-left and upper-right. This function is useful when
+ creating OpenMC mesh tallies as the bounding box is required in this form"""
+
+ return get_bounding_box(self.solid)
- @largest_shapes.setter
- def largest_shapes(self, value):
- if not isinstance(value, (list, tuple, type(None))):
- raise ValueError("paramak.Reactor.largest_shapes should be a " "list of paramak.Shapes")
- self._largest_shapes = value
+ @bounding_box.setter
+ def bounding_box(self, value):
+ self._bounding_box = value
@property
def shapes_and_components(self):
@@ -235,10 +217,12 @@ def export_dagmc_h5m(
min_mesh_size: float = 5,
max_mesh_size: float = 20,
exclude: List[str] = None,
- verbose=False,
- volume_atol=0.000001,
- center_atol=0.000001,
- bounding_box_atol=0.000001,
+ verbose: bool = False,
+ volume_atol: float = 0.000001,
+ center_atol: float = 0.000001,
+ bounding_box_atol: float = 0.000001,
+ tags: Optional[List[str]] = None,
+ include_graveyard: Optional[dict] = None,
) -> str:
"""Export a DAGMC compatible h5m file for use in neutronics simulations.
This method makes use of Gmsh to create a surface mesh of the geometry.
@@ -265,82 +249,55 @@ def export_dagmc_h5m(
bounding_box_atol: the absolute volume tolerance to allow when
matching parts in the intermediate brep file with the cadquery
parts
+ tags: the dagmc tag to use in when naming the shape in the h5m file.
+ If left as None then the Shape.name will be used. This allows
+ the DAGMC geometry created to be compatible with a wider range
+ of neutronics codes that have specific DAGMC tag requirements.
+ include_graveyard: specify if the graveyard box will be included or
+ not and how it will be sized. Leave as None if a graveyard is
+ not included. If a graveyard is required then set
+ include_graveyard to a dictionary with a key and value.
+ Acceptable keys are 'offset' and 'size'. Each key must have a
+ float value associated. For example {'size': 1000} or
+ {'offset': 10}. The size simple sets the height, width, depth
+ of the graveyard while the offset adds to the geometry to get
+ the graveyard box size.
"""
- # a local import is used here as these packages need CQ master to work
- from brep_to_h5m import brep_to_h5m
- import brep_part_finder as bpf
+ shapes_to_convert = []
- tmp_brep_filename = tempfile.mkstemp(suffix=".brep", prefix="paramak_")[1]
-
- # saves the reactor as a Brep file with merged surfaces
- self.export_brep(tmp_brep_filename)
+ for shape in self.shapes_and_components:
+ # allows components like the plasma to be removed
+ if exclude:
+ if shape.name not in exclude:
+ shapes_to_convert.append(shape)
+ else:
+ shapes_to_convert.append(shape)
- # brep file is imported
- brep_file_part_properties = bpf.get_brep_part_properties(tmp_brep_filename)
+ if include_graveyard:
+ graveyard = self.make_graveyard(**include_graveyard)
+ shapes_to_convert.append(graveyard)
- if verbose:
- print("brep_file_part_properties", brep_file_part_properties)
+ if tags is None:
+ tags = []
+ for shape in shapes_to_convert:
+ tags.append(shape.name)
- shape_properties = {}
- for shape_or_compound in self.shapes_and_components:
- sub_solid_descriptions = []
+ print(tags)
- # checks if the solid is a cq.Compound or not
- if isinstance(shape_or_compound.solid, cq.occ_impl.shapes.Compound):
- iterable_solids = shape_or_compound.solid.Solids()
- else:
- iterable_solids = shape_or_compound.solid.val().Solids()
-
- for sub_solid in iterable_solids:
- part_bb = sub_solid.BoundingBox()
- part_center = sub_solid.Center()
- sub_solid_description = {
- "volume": sub_solid.Volume(),
- "center": (part_center.x, part_center.y, part_center.z),
- "bounding_box": (
- (part_bb.xmin, part_bb.ymin, part_bb.zmin),
- (part_bb.xmax, part_bb.ymax, part_bb.zmax),
- ),
- }
- sub_solid_descriptions.append(sub_solid_description)
- shape_properties[shape_or_compound.name] = sub_solid_descriptions
-
- if verbose:
- print("shape_properties", shape_properties)
-
- # request to find part ids that are mixed up in the Brep file
- # using the volume, center, bounding box that we know about when creating the
- # CAD geometry in the first place
- key_and_part_id = bpf.get_dict_of_part_ids(
- brep_part_properties=brep_file_part_properties,
- shape_properties=shape_properties,
+ output_filename = export_solids_to_dagmc_h5m(
+ solids=[shape.solid for shape in shapes_to_convert],
+ filename=filename,
+ min_mesh_size=min_mesh_size,
+ max_mesh_size=max_mesh_size,
+ verbose=verbose,
volume_atol=volume_atol,
center_atol=center_atol,
bounding_box_atol=bounding_box_atol,
+ tags=tags,
)
- if verbose:
- print(f"key_and_part_id={key_and_part_id}")
-
- # allows components like the plasma to be removed
- if isinstance(exclude, Iterable):
- for name_to_remove in exclude:
- key_and_part_id = {key: val for key, val in key_and_part_id.items() if val != name_to_remove}
-
- brep_to_h5m(
- brep_filename=tmp_brep_filename,
- volumes_with_tags=key_and_part_id,
- h5m_filename=filename,
- min_mesh_size=min_mesh_size,
- max_mesh_size=max_mesh_size,
- delete_intermediate_stl_files=True,
- )
-
- # temporary brep is deleted
- os.remove(tmp_brep_filename)
-
- return filename
+ return output_filename
def export_stp(
self,
@@ -417,50 +374,41 @@ def export_stp(
return filename
- def export_brep(self, filename: str, merge: bool = True):
- """Exports a brep file for the Reactor.solid.
+ def export_brep(
+ self,
+ filename: str = "reactor.brep",
+ include_graveyard: Optional[dict] = None,
+ ) -> str:
+ """Exports a brep file for the Reactor. Optionally including a DAGMC
+ graveyard.
Args:
filename: the filename of exported the brep file.
- merged: if the surfaces should be merged (True) or not (False).
+ include_graveyard: specify if the graveyard box will be included or
+ not and how it will be sized. Leave as None if a graveyard is
+ not included. If a graveyard is required then set
+ include_graveyard to a dictionary with a key and value.
+ Acceptable keys are 'offset' and 'size'. Each key must have a
+ float value associated. For example {'size': 1000} or
+ {'offset': 10}. The size simple sets the height, width, depth
+ of the graveyard while the offset adds to the geometry to get
+ the graveyard box size.
Returns:
filename of the brep created
"""
- path_filename = Path(filename)
-
- if path_filename.suffix != ".brep":
- msg = "When exporting a brep file the filename must end with .brep"
- raise ValueError(msg)
-
- path_filename.parents[0].mkdir(parents=True, exist_ok=True)
-
- if not merge:
- self.solid.exportBrep(str(path_filename))
- else:
- import OCP
-
- bldr = OCP.BOPAlgo.BOPAlgo_Splitter()
+ geometry_to_save = [shape.solid for shape in self.shapes_and_components]
+ if include_graveyard:
+ graveyard = self.make_graveyard(**include_graveyard)
+ geometry_to_save.append(graveyard.solid)
- for shape in self.shapes_and_components:
- # checks if solid is a compound as .val() is not needed for compunds
- if isinstance(shape.solid, cq.occ_impl.shapes.Compound):
- bldr.AddArgument(shape.solid.wrapped)
- else:
- bldr.AddArgument(shape.solid.val().wrapped)
-
- bldr.SetNonDestructive(True)
-
- bldr.Perform()
-
- bldr.Images()
-
- merged = cq.Compound(bldr.Shape())
-
- merged.exportBrep(str(path_filename))
+ output_filename = export_solids_to_brep(
+ solids=geometry_to_save,
+ filename=filename,
+ )
- return str(path_filename)
+ return output_filename
def export_stl(
self,
@@ -573,6 +521,8 @@ def make_sector_wedge(
print("No sector wedge made as rotation angle is 360")
return None
+ # todo this should be cetered around the center point
+
if height is None:
height = self.largest_dimension * 2
@@ -666,83 +616,48 @@ def export_svg(
return str(path_filename)
- def export_stp_graveyard(
- self,
- filename: Optional[str] = "graveyard.stp",
- graveyard_size: Optional[float] = None,
- graveyard_offset: Optional[float] = None,
- ) -> str:
- """Writes a stp file (CAD geometry) for the reactor graveyard. This
- is needed for DAGMC simulations. This method also calls
- Reactor.make_graveyard() with the graveyard_size and graveyard_size
- values.
-
- Args:
- filename (str): the filename for saving the stp file. Appends
- .stp to the filename if it is missing.
- graveyard_size: directly sets the size of the graveyard. Defaults
- to None which then uses the Reactor.graveyard_size attribute.
- graveyard_offset: the offset between the largest edge of the
- geometry and inner bounding shell created. Defaults to None
- which then uses Reactor.graveyard_offset attribute.
-
- Returns:
- str: the stp filename created
- """
-
- graveyard = self.make_graveyard(
- graveyard_offset=graveyard_offset,
- graveyard_size=graveyard_size,
- )
-
- path_filename = Path(filename)
-
- if path_filename.suffix != ".stp":
- path_filename = path_filename.with_suffix(".stp")
-
- graveyard.export_stp(filename=str(path_filename))
-
- return str(path_filename)
-
def make_graveyard(
self,
- graveyard_size: Optional[float] = None,
- graveyard_offset: Optional[float] = None,
+ size: Optional[float] = None,
+ offset: Optional[float] = None,
) -> paramak.Shape:
"""Creates a graveyard volume (bounding box) that encapsulates all
volumes. This is required by DAGMC when performing neutronics
simulations. The graveyard size can be ascertained in two ways. Either
- the size can be set directly using the graveyard_size which is the
+ the size can be set directly using the size which is the
quickest method. Alternativley the graveyard can be automatically sized
- to the geometry by setting a graveyard_offset value. If both options
- are set then the method will default to using the graveyard_size
+ to the geometry by setting a offset value. If both options
+ are set then the method will default to using the size
preferentially.
Args:
- graveyard_size: directly sets the size of the graveyard. Defaults
- to None which then uses the Reactor.graveyard_size attribute.
- graveyard_offset: the offset between the largest edge of the
- geometry and inner bounding shell created. Defaults to None
- which then uses Reactor.graveyard_offset attribute.
+ size: directly sets the size of the graveyard.
+ offset: the offset between the largest edge of the geometry and
+ inner surface of the graveyard
Returns:
CadQuery solid: a shell volume that bounds the geometry, referred
to as a graveyard in DAGMC
"""
- if graveyard_size is not None:
- graveyard_size_to_use = graveyard_size
+ solid = self.solid
- elif self.graveyard_size is not None:
- graveyard_size_to_use = self.graveyard_size
+ # makes the graveyard around the center of the geometry
+ center = get_center_of_bounding_box(solid)
- elif graveyard_offset is not None:
- self.solid
- graveyard_size_to_use = self.largest_dimension * 2 + graveyard_offset * 2
+ if size is not None:
+ graveyard_size_to_use = size
+ if size <= 0:
+ raise ValueError("Graveyard size should be larger than 0")
+ largest_dim = get_largest_dimension(solid)
+ if size < largest_dim:
+ msg = f"Graveyard size should be larger than the largest shape in the Reactor. Which is {largest_dim}"
+ raise ValueError(msg)
- elif self.graveyard_offset is not None:
- self.solid
- graveyard_size_to_use = self.largest_dimension * 2 + self.graveyard_offset * 2
+ elif offset is not None:
+ graveyard_size_to_use = get_largest_dimension(solid) * 2 + offset * 2
+ if offset <= 0:
+ raise ValueError("Graveyard size should be larger than 0")
else:
raise ValueError(
@@ -751,10 +666,7 @@ def make_graveyard(
Please specify at least one of these attributes or arguments"
)
- graveyard_shape = paramak.HollowCube(
- length=graveyard_size_to_use,
- name="graveyard",
- )
+ graveyard_shape = paramak.HollowCube(length=graveyard_size_to_use, name="graveyard", center_coordinate=center)
self.graveyard = graveyard_shape
diff --git a/paramak/shape.py b/paramak/shape.py
index ec9647b82..0cbc57f9a 100644
--- a/paramak/shape.py
+++ b/paramak/shape.py
@@ -1,6 +1,4 @@
import numbers
-import os
-import tempfile
from collections.abc import Iterable
from pathlib import Path
from typing import List, Optional, Tuple, Union
@@ -18,8 +16,11 @@
facet_wire,
get_hash,
intersect_solid,
- plotly_trace,
union_solid,
+ get_largest_dimension,
+ get_bounding_box,
+ export_solids_to_brep,
+ export_solids_to_dagmc_h5m,
)
@@ -214,39 +215,28 @@ def union(self, value):
self._union = value
@property
- def largest_dimension(self):
- """Calculates a bounding box for the Shape and returns the largest
+ def largest_dimension(self) -> float:
+ """Calculates a bounding box for the Reactor and returns the largest
absolute value of the largest dimension of the bounding box"""
- largest_dimension = 0
- if isinstance(self.solid, (Compound, shapes.Solid)):
- for solid in self.solid.Solids():
- bound_box = solid.BoundingBox()
- largest_dimension = max(
- abs(bound_box.xmax),
- abs(bound_box.xmin),
- abs(bound_box.ymax),
- abs(bound_box.ymin),
- abs(bound_box.zmax),
- abs(bound_box.zmin),
- largest_dimension,
- )
- else:
- bound_box = self.solid.val().BoundingBox()
- largest_dimension = max(
- abs(bound_box.xmax),
- abs(bound_box.xmin),
- abs(bound_box.ymax),
- abs(bound_box.ymin),
- abs(bound_box.zmax),
- abs(bound_box.zmin),
- )
- self.largest_dimension = largest_dimension
- return largest_dimension
+
+ return get_largest_dimension(self.solid)
@largest_dimension.setter
def largest_dimension(self, value):
self._largest_dimension = value
+ @property
+ def bounding_box(self):
+ """Calculates a bounding box for the Shape and returns the coordinates of
+ the corners lower-left and upper-right. This function is useful when
+ creating OpenMC mesh tallies as the bounding box is required in this form"""
+
+ return get_bounding_box(self.solid)
+
+ @bounding_box.setter
+ def bounding_box(self, value):
+ self._bounding_box = value
+
@property
def workplane(self):
return self._workplane
@@ -280,19 +270,22 @@ def rotation_axis(self, value):
if len(value) != 2:
raise ValueError(msg)
for point in value:
- if not isinstance(point, tuple):
+ if not isinstance(point, Iterable):
+ msg = f"Shape.rotation_axis must be an iterable of iterables, not {type(point)}"
raise ValueError(msg)
if len(point) != 3:
+ msg = f"Shape.rotation_axis must be an iterable of iterables with 3 entries, not {len(point)}"
raise ValueError(msg)
for val in point:
if not isinstance(val, (int, float)):
+ msg = f"Shape.rotation_axis should be an iterable of iterables where the nested iterables are numerical, not {type(val)}"
raise ValueError(msg)
if value[0] == value[1]:
- msg = "The two points must be different"
+ msg = "The two coordinates points for rotation_axis must be different"
raise ValueError(msg)
elif value is not None:
- msg = "Shape.rotation_axis must be a list or a string or None"
+ msg = "Shape.rotation_axis must be an iterable or a string or None"
raise ValueError(msg)
self._rotation_axis = value
@@ -785,40 +778,53 @@ def export_stl(
return str(path_filename)
- def export_brep(self, filename):
- """Exports a brep file for the Shape.solid.
+ def export_brep(self, filename="shape.brep", include_graveyard=False) -> str:
+ """Exports a brep file for the Shape. Optionally including a DAGMC
+ graveyard.
Args:
filename: the filename of exported the brep file.
- """
+ include_graveyard: specify if the graveyard will be included or
+ not. If True the the Shape.make_graveyard will be called
+ using Shape.graveyard_size and Shape.graveyard_offset
+ attribute values.
- path_filename = Path(filename)
+ Returns:
+ filename of the brep created
+ """
- if path_filename.suffix != ".brep":
- msg = "When exporting a brep file the filename must end with .brep"
- raise ValueError(msg)
+ geometry_to_save = [self.solid]
- path_filename.parents[0].mkdir(parents=True, exist_ok=True)
+ if include_graveyard:
+ self.make_graveyard()
+ geometry_to_save.append(self.graveyard.solid)
- self.solid.val().exportBrep(str(path_filename))
- # alternative method is to use BRepTools that might support imprinting
- # and merging https://github.com/CadQuery/cadquery/issues/449
- # from OCP.BRepTools import BRepTools
- # BRepTools.Write_s(self.solid.toOCC(), str(path_filename))
+ output_filename = export_solids_to_brep(
+ solids=geometry_to_save,
+ filename=filename,
+ )
- return str(path_filename)
+ return output_filename
def export_dagmc_h5m(
self,
filename: str = "dagmc.h5m",
- min_mesh_size: float = 10,
+ min_mesh_size: float = 5,
max_mesh_size: float = 20,
+ verbose: bool = False,
+ volume_atol: float = 0.000001,
+ center_atol: float = 0.000001,
+ bounding_box_atol: float = 0.000001,
+ tags: Optional[List[str]] = None,
+ include_graveyard: bool = False,
) -> str:
"""Export a DAGMC compatible h5m file for use in neutronics simulations.
This method makes use of Gmsh to create a surface mesh of the geometry.
MOAB is used to convert the meshed geometry into a h5m with parts tagged by
using the reactor.shape_and_components.name properties. You will need
- Gmsh installed and MOAB installed to use this function.
+ Gmsh installed and MOAB installed to use this function. Acceptable
+ tolerances may need increasing to match reactor parts with the parts
+ in the intermediate Brep file produced during the process
Args:
filename: the filename of the DAGMC h5m file to write
@@ -826,31 +832,48 @@ def export_dagmc_h5m(
into gmsh.option.setNumber("Mesh.MeshSizeMin", min_mesh_size)
max_mesh_size: the maximum mesh element size to use in Gmsh. Passed
into gmsh.option.setNumber("Mesh.MeshSizeMax", max_mesh_size)
+ volume_atol: the absolute volume tolerance to allow when matching
+ parts in the intermediate brep file with the cadquery parts
+ center_atol: the absolute center coordinates tolerance to allow
+ when matching parts in the intermediate brep file with the
+ cadquery parts
+ bounding_box_atol: the absolute volume tolerance to allow when
+ matching parts in the intermediate brep file with the cadquery
+ parts
+ tags: the dagmc tag to use in when naming the shape in the h5m file.
+ If left as None then the Shape.name will be used. This allows
+ the DAGMC geometry created to be compatible with a wider range
+ of neutronics codes that have specific DAGMC tag requirements.
+ include_graveyard: specify if the graveyard will be included or
+ not. If True the the Reactor.make_graveyard will be called
+ using Reactor.graveyard_size and Reactor.graveyard_offset
+ attribute values.
"""
- from brep_to_h5m import brep_to_h5m
-
- tmp_brep_filename = tempfile.mkstemp(suffix=".brep", prefix="paramak_")[1]
+ shapes_to_convert = [self.solid]
- # saves the reactor as a Brep file with merged surfaces
- self.export_brep(tmp_brep_filename)
+ if include_graveyard:
+ self.make_graveyard()
+ shapes_to_convert.append(self.graveyard.solid)
- volumes_with_tags = {}
- for counter, _ in enumerate(self.solid.val().Solids(), 1):
- volumes_with_tags[counter] = f"mat_{self.name}"
+ if tags is None:
+ tags = [self.name]
+ if include_graveyard:
+ tags.append(self.graveyard.name)
- brep_to_h5m(
- brep_filename=tmp_brep_filename,
- volumes_with_tags=volumes_with_tags,
- h5m_filename=filename,
+ output_filename = export_solids_to_dagmc_h5m(
+ solids=shapes_to_convert,
+ filename=filename,
min_mesh_size=min_mesh_size,
max_mesh_size=max_mesh_size,
+ verbose=verbose,
+ volume_atol=volume_atol,
+ center_atol=center_atol,
+ bounding_box_atol=bounding_box_atol,
+ tags=tags,
)
- # temporary brep is deleted
- os.remove(tmp_brep_filename)
-
- return filename
+ return output_filename
def export_stp(
self,
@@ -1061,16 +1084,9 @@ def export_html(
facet_splines=facet_splines,
facet_circles=facet_circles,
tolerance=tolerance,
- title=(f"coordinates of {self.__class__.__name__} shape, viewed " "from the {view_plane} plane"),
+ title=f"coordinates of {self.__class__.__name__} shape, viewed from the {view_plane} plane",
)
- if self.points is not None:
- fig.add_trace(plotly_trace(points=self.points, mode="markers", name="Shape.points"))
-
- # sweep shapes have .path_points but not .points attribute
- if self.path_points:
- fig.add_trace(plotly_trace(points=self.path_points, mode="markers", name="Shape.path_points"))
-
if filename is not None:
Path(filename).parents[0].mkdir(parents=True, exist_ok=True)
diff --git a/paramak/utils.py b/paramak/utils.py
index a79d52de8..8bacf582f 100644
--- a/paramak/utils.py
+++ b/paramak/utils.py
@@ -7,13 +7,230 @@
from tempfile import mkstemp
from typing import List, Optional, Tuple, Union
+import tempfile
import cadquery as cq
import numpy as np
import plotly.graph_objects as go
from cadquery import importers
from OCP.GCPnts import GCPnts_QuasiUniformDeflection
+from cadquery.occ_impl import shapes
+import OCP
-import paramak
+
+def export_solids_to_brep(
+ solids: Iterable,
+ filename: str = "reactor.brep",
+):
+ """Exports a brep file for the Reactor.solid.
+
+ Args:
+ solids: a list of cadquery solids
+ filename: the filename of exported the brep file.
+
+ Returns:
+ filename of the brep created
+ """
+
+ path_filename = Path(filename)
+
+ if path_filename.suffix != ".brep":
+ msg = "When exporting a brep file the filename must end with .brep"
+ raise ValueError(msg)
+
+ path_filename.parents[0].mkdir(parents=True, exist_ok=True)
+
+ # TODO bring non merge capability back
+ # if not merge:
+ # geometry_to_save = cq.Compound.makeCompound([self.solid, self.graveyard.solid.val()])
+ # geometry_to_save.exportBrep(str(path_filename))
+
+ bldr = OCP.BOPAlgo.BOPAlgo_Splitter()
+
+ if len(solids) == 1:
+ solids[0].val().exportBrep(str(path_filename))
+ return str(path_filename)
+
+ for solid in solids:
+ # checks if solid is a compound as .val() is not needed for compounds
+ if isinstance(solid, cq.occ_impl.shapes.Compound):
+ bldr.AddArgument(solid.wrapped)
+ else:
+ bldr.AddArgument(solid.val().wrapped)
+
+ bldr.SetNonDestructive(True)
+
+ bldr.Perform()
+
+ bldr.Images()
+
+ merged_solid = cq.Compound(bldr.Shape())
+
+ merged_solid.exportBrep(str(path_filename))
+
+ return str(path_filename)
+
+
+def export_solids_to_dagmc_h5m(
+ solids: List,
+ filename: str = "dagmc.h5m",
+ min_mesh_size: float = 5,
+ max_mesh_size: float = 20,
+ verbose: bool = False,
+ volume_atol: float = 0.000001,
+ center_atol: float = 0.000001,
+ bounding_box_atol: float = 0.000001,
+ tags: List[str] = None,
+):
+ if len(tags) != len(solids):
+ msg = (
+ "When specifying tags then there must be one tag for "
+ f"every shape. Currently there are {len(tags)} tags "
+ f"provided and {len(solids)} shapes"
+ )
+ raise ValueError(msg)
+
+ # a local import is used here as these packages need Moab to work
+ from brep_to_h5m import brep_to_h5m
+ import brep_part_finder as bpf
+
+ tmp_brep_filename = tempfile.mkstemp(suffix=".brep", prefix="paramak_")[1]
+
+ # saves the reactor as a Brep file with merged surfaces
+ export_solids_to_brep(solids=solids, filename=tmp_brep_filename)
+
+ # brep file is imported
+ brep_file_part_properties = bpf.get_brep_part_properties(tmp_brep_filename)
+
+ if verbose:
+ print("brep_file_part_properties", brep_file_part_properties)
+
+ shape_properties = {}
+ for counter, solid in enumerate(solids):
+ sub_solid_descriptions = []
+
+ # checks if the solid is a cq.Compound or not
+ if isinstance(solid, cq.occ_impl.shapes.Compound):
+ iterable_solids = solid.Solids()
+ else:
+ iterable_solids = solid.val().Solids()
+
+ for sub_solid in iterable_solids:
+ part_bb = sub_solid.BoundingBox()
+ part_center = sub_solid.Center()
+ sub_solid_description = {
+ "volume": sub_solid.Volume(),
+ "center": (part_center.x, part_center.y, part_center.z),
+ "bounding_box": (
+ (part_bb.xmin, part_bb.ymin, part_bb.zmin),
+ (part_bb.xmax, part_bb.ymax, part_bb.zmax),
+ ),
+ }
+ sub_solid_descriptions.append(sub_solid_description)
+
+ shape_properties[tags[counter]] = sub_solid_descriptions
+
+ if verbose:
+ print("shape_properties", shape_properties)
+
+ # request to find part ids that are mixed up in the Brep file
+ # using the volume, center, bounding box that we know about when creating the
+ # CAD geometry in the first place
+
+ key_and_part_id = bpf.get_dict_of_part_ids(
+ brep_part_properties=brep_file_part_properties,
+ shape_properties=shape_properties,
+ volume_atol=volume_atol,
+ center_atol=center_atol,
+ bounding_box_atol=bounding_box_atol,
+ )
+
+ if verbose:
+ print(f"key_and_part_id={key_and_part_id}")
+
+ brep_to_h5m(
+ brep_filename=tmp_brep_filename,
+ volumes_with_tags=key_and_part_id,
+ h5m_filename=filename,
+ min_mesh_size=min_mesh_size,
+ max_mesh_size=max_mesh_size,
+ delete_intermediate_stl_files=True,
+ )
+
+ # temporary brep is deleted using os.remove
+ remove(tmp_brep_filename)
+
+ return filename
+
+
+def get_bounding_box(solid) -> Tuple[Tuple[float, float, float], Tuple[float, float, float]]:
+ """Calculates a bounding box for the Shape and returns the coordinates of
+ the corners lower-left and upper-right. This function is useful when
+ creating OpenMC mesh tallies as the bounding box is required in this form"""
+
+ if isinstance(solid, (cq.Compound, shapes.Solid)):
+
+ bound_box = solid.BoundingBox()
+ # previous method lopped though solids but this is not needed
+ # for single_solid in solid.Solids():
+ # bound_box = single_solid.BoundingBox()
+
+ else:
+ bound_box = solid.val().BoundingBox()
+
+ lower_left = (bound_box.xmin, bound_box.ymin, bound_box.zmin)
+
+ upper_right = (bound_box.xmax, bound_box.ymax, bound_box.zmax)
+
+ return (lower_left, upper_right)
+
+
+def get_center_of_bounding_box(solid):
+ """Calculates the geometric center of the solids bounding box"""
+
+ bounding_box = get_bounding_box(solid)
+
+ center = (
+ (bounding_box[0][0] + bounding_box[1][0]) / 2,
+ (bounding_box[0][1] + bounding_box[1][1]) / 2,
+ (bounding_box[0][2] + bounding_box[1][2]) / 2,
+ )
+
+ return center
+
+
+def get_largest_dimension(solid):
+ """Calculates the extent of the geometry in the x,y and z axis and returns
+ the largest of the three."""
+
+ bounding_box = get_bounding_box(solid)
+
+ largest_dimension = max(
+ abs(bounding_box[0][0] - bounding_box[0][1]),
+ abs(bounding_box[0][2] - bounding_box[1][0]),
+ abs(bounding_box[1][1] - bounding_box[1][2]),
+ )
+
+ return largest_dimension
+
+
+def get_largest_distance_from_origin(solid):
+ """Calculates the distance from (0, 0, 0) to the furthest part of
+ the geometry. This distance is returned as an positive value."""
+
+ bounding_box = get_bounding_box(solid)
+
+ largest_dimension = max(
+ (
+ abs(bounding_box[0][0]),
+ abs(bounding_box[0][1]),
+ abs(bounding_box[0][2]),
+ abs(bounding_box[1][0]),
+ abs(bounding_box[1][1]),
+ abs(bounding_box[1][2]),
+ )
+ )
+
+ return largest_dimension
def transform_curve(edge, tolerance: Optional[float] = 1e-3):
@@ -146,6 +363,16 @@ def diff_between_angles(angle_a: float, angle_b: float) -> float:
return delta_mod
+def angle_between_two_points_on_circle(
+ point_1: Tuple[float, float], point_2: Tuple[float, float], radius_of_circle: float
+):
+
+ separation = distance_between_two_points(point_1, point_2)
+ isos_tri_term = (2 * math.pow(radius_of_circle, 2) - math.pow(separation, 2)) / (2 * math.pow(radius_of_circle, 2))
+ angle = math.acos(isos_tri_term)
+ return angle
+
+
def distance_between_two_points(point_a: Tuple[float, float], point_b: Tuple[float, float]) -> float:
"""Computes the distance between two points.
@@ -157,8 +384,8 @@ def distance_between_two_points(point_a: Tuple[float, float], point_b: Tuple[flo
float: distance between A and B
"""
- xa, ya = point_a
- xb, yb = point_b
+ xa, ya = point_a[0], point_a[1]
+ xb, yb = point_b[0], point_b[1]
u_vec = [xb - xa, yb - ya]
return np.linalg.norm(u_vec)
@@ -174,8 +401,8 @@ def extend(point_a: Tuple[float, float], point_b: Tuple[float, float], L: float)
float, float: point C coordinates
"""
- xa, ya = point_a
- xb, yb = point_b
+ xa, ya = point_a[0], point_a[1]
+ xb, yb = point_b[0], point_b[1]
u_vec = [xb - xa, yb - ya]
u_vec /= np.linalg.norm(u_vec)
@@ -270,7 +497,7 @@ def rotate(origin: Tuple[float, float], point: Tuple[float, float], angle: float
"""
ox, oy = origin
- px, py = point
+ px, py = point[0], point[1]
qx = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy)
qy = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy)
@@ -304,7 +531,9 @@ def calculate_wedge_cut(self):
if self.rotation_angle == 360:
return None
- cutting_wedge = paramak.CuttingWedgeFS(self)
+ from paramak import CuttingWedgeFS
+
+ cutting_wedge = CuttingWedgeFS(self)
return cutting_wedge
@@ -448,9 +677,9 @@ def plotly_trace(
text_values = []
for i, point in enumerate(points):
- text = "point number= {i}
x={point[0]}
y= {point[1]}"
+ text = f"point number= {i}
x={point[0]}
y= {point[1]}"
if len(point) == 3:
- text = text + "
z= {point[2]}
"
+ text = text + f"
z= {point[2]}
"
text_values.append(text)
@@ -622,7 +851,7 @@ def export_wire_to_html(
tolerance=tolerance,
)
- points = paramak.utils.extract_points_from_edges(edges=edges, view_plane=view_plane)
+ points = extract_points_from_edges(edges=edges, view_plane=view_plane)
fig.add_trace(plotly_trace(points=points, mode=mode, name="edge " + str(counter)))
@@ -638,7 +867,7 @@ def export_wire_to_html(
# this is for cadquery generated solids
edges = wire.val().Edges()
- points = paramak.utils.extract_points_from_edges(edges=edges, view_plane=view_plane)
+ points = extract_points_from_edges(edges=edges, view_plane=view_plane)
fig.add_trace(plotly_trace(points=points, mode="markers", name="points on wire " + str(counter)))
@@ -684,9 +913,9 @@ def convert_circle_to_spline(
solid = solid.moveTo(p_0[0], p_0[1]).threePointArc(p_1, p_2)
edge = solid.vals()[0]
- new_edge = paramak.utils.transform_curve(edge, tolerance=tolerance)
+ new_edge = transform_curve(edge, tolerance=tolerance)
- points = paramak.utils.extract_points_from_edges(edges=new_edge, view_plane="XZ")
+ points = extract_points_from_edges(edges=new_edge, view_plane="XZ")
return points
diff --git a/setup.cfg b/setup.cfg
index ee8296996..2ef248554 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,7 +26,6 @@ project_urls =
packages = find:
python_requires= >=3.6
install_requires=
- pyparsing ~= 2.4.7
plotly >= 5.1.0
scipy >= 1.7.0
sympy >= 1.8
@@ -36,7 +35,7 @@ install_requires=
jupyter-client < 7
jupyter-cadquery >= 3.0.0
brep_part_finder >= 0.4.1
- brep_to_h5m >= 0.3.0
+ brep_to_h5m >= 0.3.1
setuptools_scm
[options.extras_require]
diff --git a/tests/test_parametric_components/test_constant_thickness_dome.py b/tests/test_parametric_components/test_constant_thickness_dome.py
new file mode 100644
index 000000000..d2ea6c544
--- /dev/null
+++ b/tests/test_parametric_components/test_constant_thickness_dome.py
@@ -0,0 +1,50 @@
+import unittest
+import pytest
+
+import paramak
+
+
+class TestConstantThicknessDome(unittest.TestCase):
+ """tests for the ConstantThicknessDome class"""
+
+ def test_volume_increases_with_rotation_angle(self):
+ """Tests that the volume doubles when rotation angle doubles"""
+
+ test_shape_1 = paramak.ConstantThicknessDome(rotation_angle=180)
+ test_shape_2 = paramak.ConstantThicknessDome(rotation_angle=360)
+
+ assert test_shape_1.volume() * 2 == pytest.approx(test_shape_2.volume())
+
+ def test_upper_lower_flips_points(self):
+ """Checks that the coords of the flips version are the same for p1 and p2
+ and negative for part of p3"""
+ test_shape_1 = paramak.ConstantThicknessDome(upper_or_lower="upper")
+ test_shape_2 = paramak.ConstantThicknessDome(upper_or_lower="lower")
+ assert test_shape_1.points[0] == test_shape_2.points[0]
+ assert test_shape_1.points[1] == test_shape_2.points[1]
+ assert test_shape_1.points[2][0] == test_shape_2.points[2][0]
+ assert test_shape_1.points[2][1] == -test_shape_2.points[2][1]
+
+ assert test_shape_1.volume() == pytest.approx(test_shape_2.volume())
+
+ def test_invalid_parameters_errors(self):
+ """Checks that the correct errors are raised when invalid arguments are
+ input as shape parameters."""
+
+ def incorrect_shape_height_width_ratio():
+ my_shape = paramak.ConstantThicknessDome(chord_width=10, chord_height=40)
+ my_shape.solid
+
+ def incorrect_thickness():
+ paramak.ConstantThicknessDome(thickness=-1)
+
+ def incorrect_chord_height():
+ paramak.ConstantThicknessDome(chord_height=-1)
+
+ def incorrect_chord_width():
+ paramak.ConstantThicknessDome(chord_width=-1)
+
+ self.assertRaises(ValueError, incorrect_shape_height_width_ratio)
+ self.assertRaises(ValueError, incorrect_thickness)
+ self.assertRaises(ValueError, incorrect_chord_height)
+ self.assertRaises(ValueError, incorrect_chord_width)
diff --git a/tests/test_parametric_components/test_dished_vacuum_vessel.py b/tests/test_parametric_components/test_dished_vacuum_vessel.py
new file mode 100644
index 000000000..4ff3bdd10
--- /dev/null
+++ b/tests/test_parametric_components/test_dished_vacuum_vessel.py
@@ -0,0 +1,9 @@
+import pytest
+
+import paramak
+
+
+def test_volume_increases_with_rotation_angle():
+ test_shape_1 = paramak.DishedVacuumVessel(rotation_angle=180)
+ test_shape_2 = paramak.DishedVacuumVessel(rotation_angle=360)
+ assert test_shape_1.volume() * 2 == pytest.approx(test_shape_2.volume())
diff --git a/tests/test_parametric_components/test_extrude_hollow_cube.py b/tests/test_parametric_components/test_extrude_hollow_cube.py
new file mode 100644
index 000000000..fb21dc75a
--- /dev/null
+++ b/tests/test_parametric_components/test_extrude_hollow_cube.py
@@ -0,0 +1,36 @@
+import unittest
+
+import paramak
+
+
+class TestHollowCube(unittest.TestCase):
+ """tests the hoolw cube shape that is used as a graveyard"""
+
+ def setUp(self):
+ self.test_shape = paramak.HollowCube(length=10, thickness=2)
+
+ def test_default_parameters(self):
+ """Checks that the default parameters of a HollowCube are
+ correct."""
+
+ assert self.test_shape.center_coordinate == (0.0, 0.0, 0.0)
+
+ def test_center_point_changes_bounding_box(self):
+ """Checks that moving the center results in the bounding box move as well"""
+
+ default_shape_bb = ((-(10 + 2) / 2, -(10 + 2) / 2, -(10 + 2) / 2), ((10 + 2) / 2, (10 + 2) / 2, (10 + 2) / 2))
+ assert self.test_shape.bounding_box == default_shape_bb
+
+ self.test_shape.center_coordinate = (1, 1, 1)
+
+ assert self.test_shape.bounding_box == (
+ (default_shape_bb[0][0] + 1, default_shape_bb[0][1] + 1, default_shape_bb[0][2] + 1),
+ (default_shape_bb[1][0] + 1, default_shape_bb[1][1] + 1, default_shape_bb[1][2] + 1),
+ )
+
+ self.test_shape.center_coordinate = (-2, 3, 14)
+
+ assert self.test_shape.bounding_box == (
+ (default_shape_bb[0][0] - 2, default_shape_bb[0][1] + 3, default_shape_bb[0][2] + 14),
+ (default_shape_bb[1][0] - 2, default_shape_bb[1][1] + 3, default_shape_bb[1][2] + 14),
+ )
diff --git a/tests/test_parametric_components/test_extrude_hollow_rectangle.py b/tests/test_parametric_components/test_extrude_hollow_rectangle.py
index 92274cf0b..28f645aae 100644
--- a/tests/test_parametric_components/test_extrude_hollow_rectangle.py
+++ b/tests/test_parametric_components/test_extrude_hollow_rectangle.py
@@ -37,7 +37,6 @@ def test_points_calculation(self):
"""Checks that the points used to construct the ExtrudeHollowRectangle are
calculated correctly from the parameters given."""
- print(self.test_shape.points)
assert self.test_shape.points == [
(7.5, 5.0),
(7.5, -5.0),
@@ -72,3 +71,15 @@ def test_absolute_areas(self):
assert len(set([round(i) for i in self.test_shape.areas])) == 5
assert self.test_shape.areas.count(pytest.approx(15 * 2)) == 2
assert self.test_shape.areas.count(pytest.approx(10 * 2)) == 2
+
+ def test_center_point_changes_bounding_box(self):
+
+ default_shape_bb = ((-(15 + 2) / 2, -1.0, -(10 + 2) / 2), ((15 + 2) / 2, 1.0, (10 + 2) / 2))
+ assert self.test_shape.bounding_box == default_shape_bb
+
+ self.test_shape.center_point = (1, 1)
+
+ assert self.test_shape.bounding_box == (
+ (default_shape_bb[0][0] + 1, default_shape_bb[0][1], default_shape_bb[0][2] + 1),
+ (default_shape_bb[1][0] + 1, default_shape_bb[1][1], default_shape_bb[1][2] + 1),
+ )
diff --git a/tests/test_parametric_reactors/test_ball_reactor.py b/tests/test_parametric_reactors/test_ball_reactor.py
index f5e3e9ded..54a09f5c3 100644
--- a/tests/test_parametric_reactors/test_ball_reactor.py
+++ b/tests/test_parametric_reactors/test_ball_reactor.py
@@ -32,8 +32,8 @@ def setUp(self):
def test_input_variable_names(self):
"""tests that the number of inputs variables is correct"""
- assert len(self.test_reactor.input_variables.keys()) == 28
- assert len(self.test_reactor.input_variable_names) == 28
+ assert len(self.test_reactor.input_variables.keys()) == 25
+ assert len(self.test_reactor.input_variable_names) == 25
def test_creation_with_narrow_divertor(self):
"""Creates a BallReactor with a narrow divertor and checks that the correct
diff --git a/tests/test_parametric_reactors/test_center_column_study_reactor.py b/tests/test_parametric_reactors/test_center_column_study_reactor.py
index 65ee8e844..dc6c48086 100644
--- a/tests/test_parametric_reactors/test_center_column_study_reactor.py
+++ b/tests/test_parametric_reactors/test_center_column_study_reactor.py
@@ -32,8 +32,8 @@ def setUp(self):
def test_input_variable_names(self):
"""tests that the number of inputs variables is correct"""
- assert len(self.test_reactor.input_variables.keys()) == 17
- assert len(self.test_reactor.input_variable_names) == 17
+ assert len(self.test_reactor.input_variables.keys()) == 14
+ assert len(self.test_reactor.input_variable_names) == 14
def test_creation(self):
"""Creates a ball reactor using the CenterColumnStudyReactor parametric_reactor and checks
@@ -92,19 +92,13 @@ def warning_trigger():
def test_export_brep(self):
"""Exports a brep file and checks that the output exist"""
- os.system("rm test_reactor.brep")
+ os.system("rm merged.brep")
- self.test_reactor.export_brep(filename="merged.brep", merge=True)
- self.test_reactor.export_brep(filename="not_merged.brep", merge=False)
+ self.test_reactor.export_brep(filename="merged.brep")
assert Path("merged.brep").exists() is True
- assert Path("not_merged.brep").exists() is True
- # not always true
- # assert Path("not_merged.brep").stat().st_size > Path(
- # "merged.brep").stat().st_size
os.system("rm merged.brep")
- os.system("rm not_merged.brep")
def test_export_brep_without_extention(self):
"""Exports a brep file without the extention and checks that the
diff --git a/tests/test_parametric_reactors/test_eu_demo_2015_reactor.py b/tests/test_parametric_reactors/test_eu_demo_2015_reactor.py
index af6f01714..54c8166bc 100644
--- a/tests/test_parametric_reactors/test_eu_demo_2015_reactor.py
+++ b/tests/test_parametric_reactors/test_eu_demo_2015_reactor.py
@@ -12,8 +12,8 @@ def test_input_variable_names(self):
"""tests that the number of inputs variables is correct"""
my_reactor = paramak.EuDemoFrom2015PaperDiagram(number_of_tf_coils=1)
- assert len(my_reactor.input_variables.keys()) == 5
- assert len(my_reactor.input_variable_names) == 5
+ assert len(my_reactor.input_variables.keys()) == 2
+ assert len(my_reactor.input_variable_names) == 2
def test_plasma_construction(self):
"""Creates the plasma part of the EuDemoFrom2015PaperDiagram and checks
diff --git a/tests/test_parametric_reactors/test_flf_system_code_reactor.py b/tests/test_parametric_reactors/test_flf_system_code_reactor.py
index b1986c7d4..bcdd4835b 100644
--- a/tests/test_parametric_reactors/test_flf_system_code_reactor.py
+++ b/tests/test_parametric_reactors/test_flf_system_code_reactor.py
@@ -2,7 +2,7 @@
import os
import unittest
from pathlib import Path
-
+from cadquery.occ_impl.shapes import Shape
import pytest
import paramak
@@ -28,8 +28,8 @@ def setUp(self):
def test_input_variable_names(self):
"""tests that the number of inputs variables is correct"""
- assert len(self.test_reactor.input_variables.keys()) == 13
- assert len(self.test_reactor.input_variable_names) == 13
+ assert len(self.test_reactor.input_variables.keys()) == 10
+ assert len(self.test_reactor.input_variable_names) == 10
def test_stp_file_creation(self):
"""Exports a step file and checks that it was saved successfully"""
@@ -51,6 +51,29 @@ def test_multiple_stp_file_creation(self):
assert Path("upper_vacuum_vessel.stp").is_file()
assert Path("vacuum_vessel.stp").is_file()
+ def test_graveyard_volume_in_brep_export(self):
+ """Exports the reactor as a brep file and checks the number of volumes
+ with and without the optional graveyard"""
+
+ my_reactor = paramak.FlfSystemCodeReactor()
+
+ my_reactor.export_brep(filename="without_graveyard.brep", include_graveyard=None)
+ brep_shapes = Shape.importBrep("without_graveyard.brep").Solids()
+ assert len(brep_shapes) == 6
+
+ my_reactor.export_brep(filename="with_graveyard.brep", include_graveyard={"size": 2000})
+ brep_shapes = Shape.importBrep("with_graveyard.brep").Solids()
+ assert len(brep_shapes) == 7
+
+ # TODO uncomment if ability to not merge surfaces is brought back
+ # my_reactor.export_brep(filename="without_graveyard.brep", include_graveyard=False, merge=False)
+ # brep_shapes = Shape.importBrep("without_graveyard.brep").Solids()
+ # assert len(brep_shapes) == 6
+
+ # my_reactor.export_brep(filename="with_graveyard.brep", include_graveyard=True, merge=False)
+ # brep_shapes = Shape.importBrep("with_graveyard.brep").Solids()
+ # assert len(brep_shapes) == 7
+
def test_order_of_names_in_reactor(self):
"""tests the order of Shapes in the reactor is as expected"""
diff --git a/tests/test_parametric_reactors/test_iter_reactor.py b/tests/test_parametric_reactors/test_iter_reactor.py
index 9fb0abd02..dfe5d42c6 100644
--- a/tests/test_parametric_reactors/test_iter_reactor.py
+++ b/tests/test_parametric_reactors/test_iter_reactor.py
@@ -12,8 +12,8 @@ def test_input_variable_names(self):
"""tests that the number of inputs variables is correct"""
my_reactor = paramak.IterFrom2020PaperDiagram(number_of_tf_coils=1)
- assert len(my_reactor.input_variables.keys()) == 5
- assert len(my_reactor.input_variable_names) == 5
+ assert len(my_reactor.input_variables.keys()) == 2
+ assert len(my_reactor.input_variable_names) == 2
def test_plasma_construction(self):
"""Creates the plasma part of the ITERTokamak and checks
diff --git a/tests/test_parametric_reactors/test_segmented_blanket_ball_reactor.py b/tests/test_parametric_reactors/test_segmented_blanket_ball_reactor.py
index 8c7eda029..e1a631582 100644
--- a/tests/test_parametric_reactors/test_segmented_blanket_ball_reactor.py
+++ b/tests/test_parametric_reactors/test_segmented_blanket_ball_reactor.py
@@ -37,8 +37,8 @@ def setUp(self):
def test_input_variable_names(self):
"""tests that the number of inputs variable is correct"""
- assert len(self.test_reactor.input_variables.keys()) == 31
- assert len(self.test_reactor.input_variable_names) == 31
+ assert len(self.test_reactor.input_variables.keys()) == 28
+ assert len(self.test_reactor.input_variable_names) == 28
def test_gap_between_blankets_impacts_volume(self):
"""Creates a SegmentedBlanketBallReactor with different
diff --git a/tests/test_parametric_reactors/test_single_null_ball_reactor.py b/tests/test_parametric_reactors/test_single_null_ball_reactor.py
index 14211de4d..a5434b736 100644
--- a/tests/test_parametric_reactors/test_single_null_ball_reactor.py
+++ b/tests/test_parametric_reactors/test_single_null_ball_reactor.py
@@ -39,8 +39,8 @@ def setUp(self):
def test_input_variable_names(self):
"""tests that the number of inputs variables is correct"""
- assert len(self.test_reactor.input_variables.keys()) == 28
- assert len(self.test_reactor.input_variable_names) == 28
+ assert len(self.test_reactor.input_variables.keys()) == 25
+ assert len(self.test_reactor.input_variable_names) == 25
def test_single_null_ball_reactor_with_pf_and_tf_coils(self):
"""Checks that a SingleNullBallReactor with optional pf and tf coils can
diff --git a/tests/test_parametric_reactors/test_single_null_submersion_tokamak.py b/tests/test_parametric_reactors/test_single_null_submersion_tokamak.py
index adb622717..1c396fea0 100644
--- a/tests/test_parametric_reactors/test_single_null_submersion_tokamak.py
+++ b/tests/test_parametric_reactors/test_single_null_submersion_tokamak.py
@@ -40,8 +40,8 @@ def setUp(self):
def test_input_variable_names(self):
"""tests that the number of inputs variables is correct"""
- assert len(self.test_reactor.input_variables.keys()) == 29
- assert len(self.test_reactor.input_variable_names) == 29
+ assert len(self.test_reactor.input_variables.keys()) == 26
+ assert len(self.test_reactor.input_variable_names) == 26
def test_single_null_submersion_tokamak_with_pf_and_tf_coils(self):
"""Creates a SingleNullSubmersionTokamak with pf and tf coils and checks
diff --git a/tests/test_parametric_reactors/test_sparc_2020_reactor.py b/tests/test_parametric_reactors/test_sparc_2020_reactor.py
index 7d0733d09..183d30957 100644
--- a/tests/test_parametric_reactors/test_sparc_2020_reactor.py
+++ b/tests/test_parametric_reactors/test_sparc_2020_reactor.py
@@ -64,8 +64,8 @@ def setUp(self):
def test_input_variables_names(self):
"""tests that the number of inputs variables is correct"""
- assert len(self.test_reactor.input_variables.keys()) == 28
- assert len(self.test_reactor.input_variable_names) == 28
+ assert len(self.test_reactor.input_variables.keys()) == 25
+ assert len(self.test_reactor.input_variable_names) == 25
def test_make_sparc_2020_reactor(self):
"""Runs the example to check the output files are produced"""
diff --git a/tests/test_parametric_reactors/test_submersion_tokamak.py b/tests/test_parametric_reactors/test_submersion_tokamak.py
index e86787a30..2e81b69d0 100644
--- a/tests/test_parametric_reactors/test_submersion_tokamak.py
+++ b/tests/test_parametric_reactors/test_submersion_tokamak.py
@@ -32,8 +32,8 @@ def setUp(self):
def test_input_variable_names(self):
"""tests that the number of inputs variables is correct"""
- assert len(self.test_reactor.input_variables.keys()) == 29
- assert len(self.test_reactor.input_variable_names) == 29
+ assert len(self.test_reactor.input_variables.keys()) == 26
+ assert len(self.test_reactor.input_variable_names) == 26
def test_svg_creation(self):
"""Creates a SubmersionTokamak and checks that an svg file of the
diff --git a/tests/test_parametric_shapes/test_extrude_straight_shape.py b/tests/test_parametric_shapes/test_extrude_straight_shape.py
index eb283e389..2b01b1e76 100644
--- a/tests/test_parametric_shapes/test_extrude_straight_shape.py
+++ b/tests/test_parametric_shapes/test_extrude_straight_shape.py
@@ -12,15 +12,28 @@ class TestExtrudeStraightShape(unittest.TestCase):
def setUp(self):
self.test_shape = ExtrudeStraightShape(points=[(10, 10), (10, 30), (30, 30), (30, 10)], distance=30)
+ def test_bounding_box(self):
+ """checks the bounding box value"""
+
+ assert self.test_shape.bounding_box == (
+ (10.0, -15.0, 10.0),
+ (30.0, 15.0, 30.0),
+ )
+
+ def test_largest_dimension(self):
+ """checks the largest dimension value"""
+
+ assert self.test_shape.largest_dimension == 25.0
+
def test_translate(self):
"""Checks the shape extends to the bounding box and then translates
the shape and checks it is extended to the new bounding box"""
assert self.test_shape.solid.val().BoundingBox().xmax == 30
- assert self.test_shape.solid.val().BoundingBox().xmin == 10
assert self.test_shape.solid.val().BoundingBox().ymax == 15
- assert self.test_shape.solid.val().BoundingBox().ymin == -15
assert self.test_shape.solid.val().BoundingBox().zmax == 30
+ assert self.test_shape.solid.val().BoundingBox().xmin == 10
+ assert self.test_shape.solid.val().BoundingBox().ymin == -15
assert self.test_shape.solid.val().BoundingBox().zmin == 10
self.test_shape.translate = (1, 2, 3)
diff --git a/tests/test_parametric_shapes/test_rotate_straight_shape.py b/tests/test_parametric_shapes/test_rotate_straight_shape.py
index d576285c9..14aa90d43 100644
--- a/tests/test_parametric_shapes/test_rotate_straight_shape.py
+++ b/tests/test_parametric_shapes/test_rotate_straight_shape.py
@@ -2,7 +2,7 @@
import os
import unittest
from pathlib import Path
-
+from cadquery.occ_impl.shapes import Shape
import pytest
from paramak import RotateStraightShape
@@ -305,6 +305,34 @@ def incorrect_points_definition():
self.assertRaises(ValueError, incorrect_points_definition)
+ def test_graveyard_volume_in_brep_export(self):
+ """Exports the reactor as a brep file and checks the number of volumes
+ with and without the optional graveyard"""
+
+ my_shape = RotateStraightShape(rotation_angle=20, points=[(10, 0), (10, 20), (20, 20), (20, 0)])
+
+ my_shape.export_brep(filename="without_graveyard.brep", include_graveyard=True)
+ brep_shapes = Shape.importBrep("without_graveyard.brep").Solids()
+ assert len(brep_shapes) == 2
+
+ my_shape.export_brep(filename="without_graveyard.brep", include_graveyard=False)
+ brep_shapes = Shape.importBrep("without_graveyard.brep").Solids()
+ assert len(brep_shapes) == 1
+
+ my_shape.azimuth_placement_angle = [0, 90, 180]
+
+ my_shape.export_brep(filename="with_graveyard.brep", include_graveyard=True)
+ brep_shapes = Shape.importBrep("with_graveyard.brep").Solids()
+ assert len(brep_shapes) == 4
+
+ my_shape.export_brep(filename="without_graveyard.brep", include_graveyard=False)
+ brep_shapes = Shape.importBrep("without_graveyard.brep").Solids()
+ assert len(brep_shapes) == 3
+
+ my_shape.export_brep(filename="with_graveyard.brep", include_graveyard=True)
+ brep_shapes = Shape.importBrep("with_graveyard.brep").Solids()
+ assert len(brep_shapes) == 4
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/test_reactor.py b/tests/test_reactor.py
index 0d0393aa2..d1e2da001 100644
--- a/tests/test_reactor.py
+++ b/tests/test_reactor.py
@@ -27,6 +27,18 @@ def setUp(self):
# this reactor has a compound shape in the geometry
self.test_reactor_3 = paramak.Reactor([self.test_shape, test_shape_3])
+ def test_bounding_box(self):
+ """checks the bounding box value"""
+
+ bounding_box = self.test_reactor_2.bounding_box
+
+ assert bounding_box[0][0] == pytest.approx(-20.0)
+ assert bounding_box[0][1] == pytest.approx(-20.0)
+ assert bounding_box[0][2] == pytest.approx(0.0)
+ assert bounding_box[1][0] == pytest.approx(100.0)
+ assert bounding_box[1][1] == pytest.approx(20.0)
+ assert bounding_box[1][2] == pytest.approx(100.0)
+
def test_reactor_export_stp_with_name_set_to_none(self):
"""Exports the reactor as separate files and as a single file"""
@@ -50,17 +62,16 @@ def test_reactor_export_stp(self):
def test_incorrect_graveyard_offset_too_small(self):
def incorrect_graveyard_offset_too_small():
- """Set graveyard_offset as a negative number which should raise an error"""
+ """Set graveyard offset as a negative number which should raise an error"""
- self.test_reactor.graveyard_offset = -3
+ self.test_reactor.make_graveyard(offset=-3)
self.assertRaises(ValueError, incorrect_graveyard_offset_too_small)
def test_incorrect_graveyard_offset_wrong_type(self):
def incorrect_graveyard_offset_wrong_type():
- """Set graveyard_offset as a string which should raise an error"""
-
- self.test_reactor.graveyard_offset = "coucou"
+ """Set graveyard offset as a string which should raise an error"""
+ self.test_reactor.make_graveyard(offset="coucou")
self.assertRaises(TypeError, incorrect_graveyard_offset_wrong_type)
@@ -68,16 +79,14 @@ def test_largest_dimension_setting_and_getting_using_largest_shapes(self):
"""Makes a neutronics model and checks the default largest_dimension
and that largest_dimension changes with largest_shapes"""
- assert self.test_reactor.largest_dimension == 20.0
+ assert pytest.approx(self.test_reactor.largest_dimension) == 20.0
+ assert self.test_reactor_2.largest_dimension == 100.0
test_shape = paramak.RotateStraightShape(points=[(0, 0), (0, 20), (20, 20)])
test_shape2 = paramak.RotateStraightShape(points=[(0, 0), (0, 40), (40, 40)])
test_reactor = paramak.Reactor([test_shape, test_shape2])
- assert test_reactor.largest_dimension == 40
-
- test_reactor.largest_shapes = [test_shape]
- assert test_reactor.largest_dimension == 20
+ assert pytest.approx(test_reactor.largest_dimension) == 40
def test_make_sector_wedge(self):
"""Checks that the wedge is not made when rotation angle is 360"""
@@ -94,7 +103,7 @@ def test_stl_filename_list_length():
def test_make_graveyard_accepts_offset_from_graveyard(self):
"""Creates a graveyard for a reactor and sets the graveyard_offset.
- Checks that the Reactor.graveyard_offset property is set"""
+ Checks that the Reactor.graveyard property is set"""
test_shape = paramak.RotateStraightShape(
points=[(0, 0), (0, 20), (20, 20)],
@@ -104,9 +113,10 @@ def test_make_graveyard_accepts_offset_from_graveyard(self):
)
test_shape.rotation_angle = 360
test_reactor = paramak.Reactor([test_shape, test_shape2])
- test_reactor.graveyard_offset == 101
- graveyard = test_reactor.make_graveyard()
+
+ graveyard = test_reactor.make_graveyard(offset=101)
assert graveyard.volume() > 0
+ assert test_reactor.graveyard.volume() > 0
def test_reactor_creation_with_default_properties(self):
"""creates a Reactor object and checks that it has no default properties"""
@@ -134,7 +144,7 @@ def test_graveyard_exists(self):
test_shape.rotation_angle = 360
test_shape.create_solid()
test_reactor = paramak.Reactor([test_shape])
- test_reactor.make_graveyard()
+ test_reactor.make_graveyard(size=100)
assert isinstance(test_reactor.graveyard, paramak.Shape)
@@ -148,7 +158,7 @@ def test_graveyard_exists_solid_is_none(self):
test_shape.create_solid()
test_reactor = paramak.Reactor([test_shape])
test_reactor.shapes_and_components[0].solid = None
- test_reactor.make_graveyard()
+ test_reactor.make_graveyard(size=100)
assert isinstance(test_reactor.graveyard, paramak.Shape)
@@ -162,14 +172,15 @@ def test_export_graveyard(self):
os.system("rm graveyard.stp")
test_reactor = paramak.Reactor([test_shape])
- test_reactor.export_stp_graveyard()
- test_reactor.export_stp_graveyard(filename="my_graveyard.stp")
- test_reactor.export_stp_graveyard(filename="my_graveyard_without_ext")
+ test_reactor.make_graveyard(size=100)
+ test_reactor.graveyard.export_stp(filename="graveyard.stp")
+ test_reactor.graveyard.export_stp(filename="my_graveyard.stp")
+ test_reactor.graveyard.export_stp(filename="my_graveyard_without_ext.step")
for filepath in [
"graveyard.stp",
"my_graveyard.stp",
- "my_graveyard_without_ext.stp",
+ "my_graveyard_without_ext.step",
]:
assert Path(filepath).exists() is True
os.system("rm " + filepath)
@@ -183,16 +194,16 @@ def test_make_graveyard_offset(self):
test_shape = paramak.RotateStraightShape(points=[(0, 0), (0, 20), (20, 20)])
os.system("rm graveyard.stp")
- test_reactor = paramak.Reactor([test_shape], graveyard_size=None, graveyard_offset=100)
- test_reactor.make_graveyard()
+ test_reactor = paramak.Reactor([test_shape])
+ test_reactor.make_graveyard(offset=100)
graveyard_volume_1 = test_reactor.graveyard.volume()
- test_reactor.make_graveyard(graveyard_offset=50)
+ test_reactor.make_graveyard(offset=50)
assert test_reactor.graveyard.volume() < graveyard_volume_1
graveyard_volume_2 = test_reactor.graveyard.volume()
- test_reactor.make_graveyard(graveyard_offset=200)
+ test_reactor.make_graveyard(offset=200)
assert test_reactor.graveyard.volume() > graveyard_volume_1
assert test_reactor.graveyard.volume() > graveyard_volume_2
@@ -375,27 +386,30 @@ def test_graveyard_size_setting_magnitude_checking(self):
def incorrect_graveyard_size_size():
test_shape = paramak.RotateStraightShape(points=[(0, 0), (0, 20), (20, 20)])
- paramak.Reactor([test_shape], graveyard_size=-10)
+ test_reactor = paramak.Reactor([test_shape])
+ test_reactor.make_graveyard(size=-10)
self.assertRaises(ValueError, incorrect_graveyard_size_size)
def test_graveyard_offset_setting_type_checking(self):
- """Attempts to make a reactor with a graveyard_offset that is an float
+ """Attempts to make a reactor with a graveyard offset that is an float
which should raise a ValueError"""
def incorrect_graveyard_offset_type():
test_shape = paramak.RotateStraightShape(points=[(0, 0), (0, 20), (20, 20)])
- paramak.Reactor([test_shape], graveyard_offset="coucou")
+ test_reactor = paramak.Reactor([test_shape])
+ test_reactor.make_graveyard(offset="coucou")
self.assertRaises(TypeError, incorrect_graveyard_offset_type)
def test_graveyard_offset_setting_magnitude_checking(self):
- """Attempts to make a reactor with a graveyard_offset that is an int
+ """Attempts to make a reactor with a graveyard offset that is an int
which should raise a ValueError"""
def incorrect_graveyard_offset_size():
test_shape = paramak.RotateStraightShape(points=[(0, 0), (0, 20), (20, 20)])
- paramak.Reactor([test_shape], graveyard_offset=-10)
+ test_reactor = paramak.Reactor([test_shape])
+ test_reactor.make_graveyard(size=-10)
self.assertRaises(ValueError, incorrect_graveyard_offset_size)
@@ -404,17 +418,17 @@ def test_graveyard_error(self):
test_reactor = paramak.Reactor([test_shape])
def str_graveyard_offset():
- test_reactor.graveyard_offset = "coucou"
+ test_reactor.make_graveyard(offset="coucou")
self.assertRaises(TypeError, str_graveyard_offset)
def negative_graveyard_offset():
- test_reactor.graveyard_offset = -2
+ test_reactor.make_graveyard(offset=-2)
self.assertRaises(ValueError, negative_graveyard_offset)
def list_graveyard_offset():
- test_reactor.graveyard_offset = [1.2]
+ test_reactor.make_graveyard(offset=[1.2])
self.assertRaises(TypeError, list_graveyard_offset)
@@ -427,7 +441,7 @@ def test_compound_in_shapes(self):
assert test_reactor.solid is not None
def test_sector_wedge_with_360_returns_none(self):
- """Trys to make a sector wedge with full 360 degree rotation and checks
+ """Tries to make a sector wedge with full 360 degree rotation and checks
that None is returned"""
test_shape = paramak.RotateStraightShape(points=[(0, 0), (0, 20), (20, 20)])
diff --git a/tests/test_shape.py b/tests/test_shape.py
index 4c6aefd1d..75c3df995 100644
--- a/tests/test_shape.py
+++ b/tests/test_shape.py
@@ -595,7 +595,7 @@ def test_rotation_axis_error(self):
[(1, 1, 1), (1, 1, 1)],
[(1, 1, 1), (1, 0, 1, 2)],
[(1, 1, 1, 2), (1, 0, 2)],
- [(1, 1, 2), [1, 0, 2]],
+ # [(1, 1, 2), [1, 0, 2]], lists are now acceptable
[(1, 1, 1)],
[(1, 1, 1), (1, "coucou", 1)],
[(1, 1, 1), (1, 0, 1), (1, 2, 3)],
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 753edac95..f42ed3fd0 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -12,10 +12,105 @@
find_center_point_of_circle,
plotly_trace,
find_radius_of_circle,
+ get_bounding_box,
+ get_largest_dimension,
+ get_largest_distance_from_origin,
)
+import cadquery as cq
class TestUtilityFunctions(unittest.TestCase):
+ """ "tests the utility functions"""
+
+ def test_bounding_box_with_single_shape_at_origin(self):
+ """checks the type and values of the bounding box returned"""
+
+ test_sphere = cq.Workplane("XY").moveTo(0, 0).sphere(10)
+
+ bounding_box = get_bounding_box(test_sphere)
+
+ assert len(bounding_box) == 2
+ assert len(bounding_box[0]) == 3
+ assert len(bounding_box[1]) == 3
+ assert bounding_box[0][0] == -10
+ assert bounding_box[0][1] == -10
+ assert bounding_box[0][2] == -10
+ assert bounding_box[1][0] == 10
+ assert bounding_box[1][1] == 10
+ assert bounding_box[1][2] == 10
+
+ def test_bounding_box_with_single_shape(self):
+ """checks the type and values of the bounding box returned"""
+
+ test_sphere = cq.Workplane("XY").moveTo(100, 50).sphere(10)
+
+ bounding_box = get_bounding_box(test_sphere)
+
+ assert len(bounding_box) == 2
+ assert len(bounding_box[0]) == 3
+ assert len(bounding_box[1]) == 3
+ assert bounding_box[0][0] == 90
+ assert bounding_box[0][1] == 40
+ assert bounding_box[0][2] == -10
+ assert bounding_box[1][0] == 110
+ assert bounding_box[1][1] == 60
+ assert bounding_box[1][2] == 10
+
+ def test_bounding_box_with_compound(self):
+ """checks the type and values of the bounding box returned"""
+
+ test_sphere_1 = cq.Workplane("XY").moveTo(100, 50).sphere(10)
+ test_sphere_2 = cq.Workplane("XY").moveTo(-100, -50).sphere(10)
+
+ both_shapes = cq.Compound.makeCompound([test_sphere_1.val(), test_sphere_2.val()])
+
+ bounding_box = get_bounding_box(both_shapes)
+
+ assert len(bounding_box) == 2
+ assert len(bounding_box[0]) == 3
+ assert len(bounding_box[1]) == 3
+ assert bounding_box[0][0] == -110
+ assert bounding_box[0][1] == -60
+ assert bounding_box[0][2] == -10
+ assert bounding_box[1][0] == 110
+ assert bounding_box[1][1] == 60
+ assert bounding_box[1][2] == 10
+
+ def test_largest_dimension_with_single_solid_at_origin(self):
+
+ test_sphere = cq.Workplane("XY").moveTo(0, 0).sphere(10)
+
+ largest_dimension = get_largest_dimension(test_sphere)
+
+ assert largest_dimension == 20
+
+ def test_largest_dimension__from_origin_with_single_solid_at_origin(self):
+
+ test_sphere = cq.Workplane("XY").moveTo(0, 0).sphere(10)
+
+ largest_dimension = get_largest_distance_from_origin(test_sphere)
+
+ assert largest_dimension == 10
+
+ def test_largest_dimension_with_single_solid(self):
+
+ test_sphere = cq.Workplane("XY").moveTo(100, 0).sphere(10)
+
+ largest_dimension = get_largest_distance_from_origin(test_sphere)
+
+ assert largest_dimension == 110
+
+ def test_largest_dimension_with_compound(self):
+
+ test_sphere_1 = cq.Workplane("XY").moveTo(100, 50).sphere(10)
+ test_sphere_2 = cq.Workplane("XY").moveTo(-200, -50).sphere(10)
+
+ both_shapes = cq.Compound.makeCompound([test_sphere_1.val(), test_sphere_2.val()])
+
+ largest_dimension = get_largest_distance_from_origin(both_shapes)
+
+ assert largest_dimension == 210
+
def test_convert_circle_to_spline(self):
"""Tests the conversion of 3 points on a circle into points on a spline
curve."""
diff --git a/tests_h5m/test_reactor_export_h5m.py b/tests_h5m/test_reactor_export_h5m.py
index 97b61de03..60d5fb240 100644
--- a/tests_h5m/test_reactor_export_h5m.py
+++ b/tests_h5m/test_reactor_export_h5m.py
@@ -26,25 +26,95 @@ def setUp(self):
# this reactor has a compound shape in the geometry
self.test_reactor_3 = paramak.Reactor([self.test_shape, test_shape_3])
+ def test_dagmc_h5m_custom_tags_export(self):
+ """Exports a reactor with two shapes checks that the tags are correctly
+ named in the resulting h5m file"""
+
+ self.test_reactor_3.rotation_angle = 180
+ self.test_reactor_3.export_dagmc_h5m("dagmc_reactor.h5m", tags=["1", "2"])
+
+ vols = di.get_volumes_from_h5m("dagmc_reactor.h5m")
+ assert vols == [1, 2, 3] # there are three volumes in test_reactor_3
+
+ mats = di.get_materials_from_h5m("dagmc_reactor.h5m")
+ print(mats)
+ assert mats == ["1", "2"]
+
+ vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_reactor.h5m")
+ assert vols_and_mats == {
+ 1: "1",
+ 2: "2",
+ 3: "2",
+ }
+
def test_dagmc_h5m_export(self):
- """Exports a shape with a single volume and checks that it
- exist (volume id and material tag) in the resulting h5m file"""
+ """Exports a reactor with two shapes checks that the tags are correctly
+ named in the resulting h5m file"""
self.test_reactor_3.rotation_angle = 180
self.test_reactor_3.export_dagmc_h5m("dagmc_reactor.h5m")
vols = di.get_volumes_from_h5m("dagmc_reactor.h5m")
- assert vols == [1, 2, 3] # there are three volumes in test_reactor_3
+ assert vols == [1, 2, 3] # there are two shapes three volumes in test_reactor_3
+
+ mats = di.get_materials_from_h5m("dagmc_reactor.h5m")
+ print(mats)
+ assert mats == ["pf_coil", "test_shape"]
+
+ vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_reactor.h5m")
+ assert vols_and_mats == {
+ 1: "test_shape",
+ 2: "pf_coil",
+ 3: "pf_coil",
+ }
+
+ def test_dagmc_h5m_custom_tags_export_with_graveyard(self):
+ """Exports a reactor with two shapes checks that the tags are correctly
+ named in the resulting h5m file, includes the optional graveyard"""
+
+ self.test_reactor_3.rotation_angle = 180
+ self.test_reactor_3.export_dagmc_h5m(
+ "dagmc_reactor.h5m", tags=["1", "2", "grave"], include_graveyard={"size": 250}
+ )
+
+ vols = di.get_volumes_from_h5m("dagmc_reactor.h5m")
+ assert vols == [1, 2, 3, 4]
+
+ mats = di.get_materials_from_h5m("dagmc_reactor.h5m")
+ print(mats)
+ assert mats == ["1", "2", "grave"]
+
+ vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_reactor.h5m")
+ assert vols_and_mats == {
+ 1: "1",
+ 2: "2",
+ 3: "2",
+ 4: "grave",
+ }
+
+ def test_dagmc_h5m_export_with_graveyard(self):
+ """Exports a reactor with two shapes checks that the tags are correctly
+ named in the resulting h5m file, includes the optional graveyard"""
+
+ self.test_reactor_3.rotation_angle = 180
+ self.test_reactor_3.export_dagmc_h5m("dagmc_reactor.h5m", include_graveyard={"size": 250})
+
+ vols = di.get_volumes_from_h5m("dagmc_reactor.h5m")
+ assert vols == [1, 2, 3, 4]
mats = di.get_materials_from_h5m("dagmc_reactor.h5m")
print(mats)
- assert mats == ["mat_pf_coil", "mat_test_shape"]
+ assert "test_shape" in mats
+ assert "pf_coil" in mats
+ assert "graveyard" in mats
+ assert len(mats) == 3
vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_reactor.h5m")
assert vols_and_mats == {
- 1: "mat_test_shape",
- 2: "mat_pf_coil",
- 3: "mat_pf_coil",
+ 1: "test_shape",
+ 2: "pf_coil",
+ 3: "pf_coil",
+ 4: "graveyard",
}
def test_dagmc_h5m_export_mesh_size(self):
@@ -56,6 +126,21 @@ def test_dagmc_h5m_export_mesh_size(self):
assert Path("dagmc_bigger.h5m").stat().st_size > Path("dagmc_default.h5m").stat().st_size
+ def test_dagmc_h5m_export_error_handling(self):
+ """Exports a shape with the wrong amount of tags"""
+
+ def too_few_tags():
+ self.test_reactor_3.rotation_angle = 180
+ self.test_reactor_3.export_dagmc_h5m("dagmc_reactor.h5m", tags=["1"])
+
+ self.assertRaises(ValueError, too_few_tags)
+
+ def too_many_tags():
+ self.test_reactor_3.rotation_angle = 180
+ self.test_reactor_3.export_dagmc_h5m("dagmc_reactor.h5m", tags=["1", "2", "3"])
+
+ self.assertRaises(ValueError, too_many_tags)
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests_h5m/test_rotate_straight_shape_export_h5m.py b/tests_h5m/test_rotate_straight_shape_export_h5m.py
index 880b9a28f..9537aaa5d 100644
--- a/tests_h5m/test_rotate_straight_shape_export_h5m.py
+++ b/tests_h5m/test_rotate_straight_shape_export_h5m.py
@@ -10,6 +10,7 @@ class TestRotateStraightShape(unittest.TestCase):
def setUp(self):
self.test_shape = RotateStraightShape(points=[(0, 0), (0, 20), (20, 20), (20, 0)])
+ self.test_shape.graveyard_size = 100
def test_dagmc_h5m_export_multi_volume(self):
"""Exports a shape with multiple volumes and checks that they all
@@ -24,14 +25,61 @@ def test_dagmc_h5m_export_multi_volume(self):
assert vols == [1, 2, 3, 4]
mats = di.get_materials_from_h5m("dagmc_multi_volume.h5m")
- assert mats == ["mat_my_material_name"]
+ assert mats == ["my_material_name"]
vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_multi_volume.h5m")
assert vols_and_mats == {
- 1: "mat_my_material_name",
- 2: "mat_my_material_name",
- 3: "mat_my_material_name",
- 4: "mat_my_material_name",
+ 1: "my_material_name",
+ 2: "my_material_name",
+ 3: "my_material_name",
+ 4: "my_material_name",
+ }
+
+ def test_dagmc_h5m_export_custom_tag_multi_volume(self):
+ """Exports a shape with multiple volumes and checks that they all
+ exist (volume ids and material tags) in the resulting h5m file"""
+
+ self.test_shape.rotation_angle = 10
+ self.test_shape.azimuth_placement_angle = [0, 90, 180, 270]
+ self.test_shape.name = "my_material_name"
+ self.test_shape.export_dagmc_h5m("dagmc_multi_volume.h5m", tags=["1"])
+
+ vols = di.get_volumes_from_h5m("dagmc_multi_volume.h5m")
+ assert vols == [1, 2, 3, 4]
+
+ mats = di.get_materials_from_h5m("dagmc_multi_volume.h5m")
+ assert mats == ["1"]
+
+ vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_multi_volume.h5m")
+ assert vols_and_mats == {
+ 1: "1",
+ 2: "1",
+ 3: "1",
+ 4: "1",
+ }
+
+ def test_dagmc_h5m_export_custom_tag_multi_volume_with_graveyard(self):
+ """Exports a shape with multiple volumes and checks that they all
+ exist (volume ids and material tags) in the resulting h5m file"""
+
+ self.test_shape.rotation_angle = 10
+ self.test_shape.azimuth_placement_angle = [0, 90, 180, 270]
+ self.test_shape.name = "my_material_name"
+ self.test_shape.export_dagmc_h5m("dagmc_multi_volume.h5m", tags=["1", "graveyard"], include_graveyard=True)
+
+ vols = di.get_volumes_from_h5m("dagmc_multi_volume.h5m")
+ assert vols == [1, 2, 3, 4, 5]
+
+ mats = di.get_materials_from_h5m("dagmc_multi_volume.h5m")
+ assert mats == ["1", "graveyard"]
+
+ vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_multi_volume.h5m")
+ assert vols_and_mats == {
+ 1: "1",
+ 2: "1",
+ 3: "1",
+ 4: "1",
+ 5: "graveyard",
}
def test_dagmc_h5m_export_single_volume(self):
@@ -46,10 +94,45 @@ def test_dagmc_h5m_export_single_volume(self):
assert vols == [1]
mats = di.get_materials_from_h5m("dagmc_single_volume.h5m")
- assert mats == ["mat_my_material_name_single"]
+ assert mats == ["my_material_name_single"]
+
+ vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_single_volume.h5m")
+ assert vols_and_mats == {1: "my_material_name_single"}
+
+ def test_dagmc_h5m_export_single_volume_with_graveyard(self):
+ """Exports a shape with a single volume plus graveyard cell and checks
+ that it exist (volume id and material tag) in the resulting h5m file"""
+
+ self.test_shape.rotation_angle = 180
+ self.test_shape.name = "my_material_name_single"
+ self.test_shape.export_dagmc_h5m(filename="dagmc_single_volume.h5m", include_graveyard=True)
+
+ vols = di.get_volumes_from_h5m("dagmc_single_volume.h5m")
+ assert vols == [1, 2]
+
+ mats = di.get_materials_from_h5m("dagmc_single_volume.h5m")
+ assert "my_material_name_single" in mats
+ assert "graveyard" in mats
+ assert len(mats) == 2
+
+ vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_single_volume.h5m")
+ assert vols_and_mats == {1: "my_material_name_single", 2: "graveyard"}
+
+ def test_dagmc_h5m_export_single_volume_custom_tags(self):
+ """Exports a shape with a single volume and checks that it
+ exist (volume id and custom material tag) in the resulting h5m file"""
+
+ self.test_shape.rotation_angle = 180
+ self.test_shape.export_dagmc_h5m("dagmc_custom_tag_single_volume.h5m", tags=["1"])
+
+ vols = di.get_volumes_from_h5m("dagmc_custom_tag_single_volume.h5m")
+ assert vols == [1]
+
+ mats = di.get_materials_from_h5m("dagmc_custom_tag_single_volume.h5m")
+ assert mats == ["1"]
vols_and_mats = di.get_volumes_and_materials_from_h5m("dagmc_single_volume.h5m")
- assert vols_and_mats == {1: "mat_my_material_name_single"}
+ assert vols_and_mats == {1: "my_material_name_single"}
def test_dagmc_h5m_export_mesh_size(self):
"""Exports h5m file with higher resolution mesh and checks that the