diff --git a/examples/example_parametric_components/make_all_parametric_components.py b/examples/example_parametric_components/make_all_parametric_components.py index bcee900e0..4160bc5eb 100644 --- a/examples/example_parametric_components/make_all_parametric_components.py +++ b/examples/example_parametric_components/make_all_parametric_components.py @@ -334,7 +334,7 @@ def main(): component = paramak.PortCutterRectangular( distance=3, - z_pos=0, + center_point=(0, 0), height=0.2, width=0.4, fillet_radius=0.02, @@ -344,7 +344,7 @@ def main(): component = paramak.PortCutterCircular( distance=3, - z_pos=0.25, + center_point=(0.25, 0), radius=0.1, # azimuth_placement_angle=[0, 45, 90, 180], # TODO: fix issue #548 azimuth_placement_angle=[0, 45, 90], diff --git a/examples/example_parametric_components/make_vacuum_vessel_with_ports.py b/examples/example_parametric_components/make_vacuum_vessel_with_ports.py index 9085eb41a..16b3d2b61 100644 --- a/examples/example_parametric_components/make_vacuum_vessel_with_ports.py +++ b/examples/example_parametric_components/make_vacuum_vessel_with_ports.py @@ -25,7 +25,7 @@ def main(): # makes the middle row of ports circular_ports = paramak.PortCutterCircular( distance=5, - z_pos=0, + center_point=(0, 0), radius=0.2, azimuth_placement_angle=angles_for_ports ) @@ -33,7 +33,7 @@ def main(): # makes the lower row of ports rectangular_ports = paramak.PortCutterRectangular( distance=5, - z_pos=-1, + center_point=(-1, 0), height=0.3, width=0.4, fillet_radius=0.08, diff --git a/paramak/parametric_components/port_cutters_circular.py b/paramak/parametric_components/port_cutters_circular.py index b6aa508d0..38a672127 100644 --- a/paramak/parametric_components/port_cutters_circular.py +++ b/paramak/parametric_components/port_cutters_circular.py @@ -1,4 +1,6 @@ +from typing import Optional + from paramak import ExtrudeCircleShape @@ -7,30 +9,33 @@ class PortCutterCircular(ExtrudeCircleShape): other components (eg. blanket, vessel,..) in order to create ports. Args: - z_pos (float): Z position (cm) of the port - height (float): height (cm) of the port - width (float): width (cm) of the port - distance (float): extruded distance (cm) of the cutter - stp_filename (str, optional): defaults to "PortCutterCircular.stp". - stl_filename (str, optional): defaults to "PortCutterCircular.stl". - name (str, optional): defaults to "circular_port_cutter". - material_tag (str, optional): defaults to "circular_port_cutter_mat". - extrusion_start_offset (float, optional): the distance between 0 and - the start of the extrusion. Defaults to 1.. + radius: radius (cm) of port cutter. + distance: extruded distance (cm) of the port cutter. + center_point: center point of the port cutter. Defaults to (0, 0). + workplane: workplane in which the port cutters are created. Defaults + to "ZY". + rotation_axis: axis around which the port cutters are rotated and + placed. Defaults to "Z". + extrusion_start_offset: the distance between 0 and the start of the + extrusion. Defaults to 1.. + stp_filename: defaults to "PortCutterCircular.stp". + stl_filename: defaults to "PortCutterCircular.stl". + name: defaults to "circular_port_cutter". + material_tag: defaults to "circular_port_cutter_mat". """ def __init__( self, - z_pos, - radius, - distance, - workplane="ZY", - rotation_axis="Z", - extrusion_start_offset=1., - stp_filename="PortCutterCircular.stp", - stl_filename="PortCutterCircular.stl", - name="circular_port_cutter", - material_tag="circular_port_cutter_mat", + radius: float, + distance: float, + center_point: Optional[tuple] = (0, 0), + workplane: Optional[str] = "ZY", + rotation_axis: Optional[str] = "Z", + extrusion_start_offset: Optional[float] = 1., + stp_filename: Optional[str] = "PortCutterCircular.stp", + stl_filename: Optional[str] = "PortCutterCircular.stl", + name: Optional[str] = "circular_port_cutter", + material_tag: Optional[str] = "circular_port_cutter_mat", **kwargs ): super().__init__( @@ -47,8 +52,8 @@ def __init__( **kwargs ) - self.z_pos = z_pos self.radius = radius + self.center_point = center_point def find_points(self): - self.points = [(0, self.z_pos)] + self.points = [self.center_point] diff --git a/paramak/parametric_components/port_cutters_rectangular.py b/paramak/parametric_components/port_cutters_rectangular.py index 4ac5fc9b4..73b366417 100644 --- a/paramak/parametric_components/port_cutters_rectangular.py +++ b/paramak/parametric_components/port_cutters_rectangular.py @@ -1,4 +1,6 @@ +from typing import Optional + from paramak import ExtrudeStraightShape @@ -7,10 +9,16 @@ class PortCutterRectangular(ExtrudeStraightShape): other components (eg. blanket, vessel,..) in order to create ports. Args: - z_pos (float): Z position (cm) of the port - height (float): height (cm) of the port - width (float): width (cm) of the port - distance (float): extruded distance (cm) of the cutter + height: height (cm) of the port cutter. + width: width (cm) of the port cutter. + distance: extruded distance (cm) of the port cutter. + center_point: Center point of the port cutter. Defaults to (0, 0). + workplane: workplane in which the port cutters are created. Defaults + to "ZY". + rotation_axis: axis around which the port cutters are rotated and + placed. Defaults to "Z". + extrusion_start_offset (float, optional): the distance between 0 and + the start of the extrusion. Defaults to 1.. fillet_radius (float, optional): If not None, radius (cm) of fillets added to edges orthogonal to the Z direction. Defaults to None. stp_filename (str, optional): defaults to "PortCutterRectangular.stp". @@ -18,24 +26,22 @@ class PortCutterRectangular(ExtrudeStraightShape): name (str, optional): defaults to "rectangular_port_cutter". material_tag (str, optional): defaults to "rectangular_port_cutter_mat". - extrusion_start_offset (float, optional): the distance between 0 and - the start of the extrusion. Defaults to 1.. """ def __init__( self, - z_pos, - height, - width, - distance, - workplane="ZY", - rotation_axis="Z", - extrusion_start_offset=1., - fillet_radius=None, - stp_filename="PortCutterRectangular.stp", - stl_filename="PortCutterRectangular.stl", - name="rectangular_port_cutter", - material_tag="rectangular_port_cutter_mat", + height: float, + width: float, + distance: float, + center_point: Optional[tuple] = (0, 0), + workplane: Optional[str] = "ZY", + rotation_axis: Optional[str] = "Z", + extrusion_start_offset: Optional[float] = 1., + fillet_radius: Optional[float] = None, + stp_filename: Optional[str] = "PortCutterRectangular.stp", + stl_filename: Optional[str] = "PortCutterRectangular.stl", + name: Optional[str] = "rectangular_port_cutter", + material_tag: Optional[str] = "rectangular_port_cutter_mat", **kwargs ): @@ -52,22 +58,39 @@ def __init__( **kwargs ) - self.z_pos = z_pos self.height = height self.width = width + self.center_point = center_point self.fillet_radius = fillet_radius - self.add_fillet() def find_points(self): + if self.workplane[0] < self.workplane[1]: + parameter_1 = self.width + parameter_2 = self.height + else: + parameter_1 = self.height + parameter_2 = self.width + points = [ - (-self.width / 2, -self.height / 2), - (self.width / 2, -self.height / 2), - (self.width / 2, self.height / 2), - (-self.width / 2, self.height / 2), + (-parameter_1 / 2, parameter_2 / 2), + (parameter_1 / 2, parameter_2 / 2), + (parameter_1 / 2, -parameter_2 / 2), + (-parameter_1 / 2, -parameter_2 / 2), ] - points = [(e[0], e[1] + self.z_pos) for e in points] + points = [(e[0] + self.center_point[0], e[1] + + self.center_point[1]) for e in points] + self.points = points - def add_fillet(self): + def add_fillet(self, solid): + if "X" not in self.workplane: + filleting_edge = "|X" + if "Y" not in self.workplane: + filleting_edge = "|Y" + if "Z" not in self.workplane: + filleting_edge = "|Z" + if self.fillet_radius is not None and self.fillet_radius != 0: - self.solid = self.solid.edges('#Z').fillet(self.fillet_radius) + solid = solid.edges(filleting_edge).fillet(self.fillet_radius) + + return solid diff --git a/paramak/parametric_shapes/extruded_mixed_shape.py b/paramak/parametric_shapes/extruded_mixed_shape.py index 0e19acc2c..027d5d362 100644 --- a/paramak/parametric_shapes/extruded_mixed_shape.py +++ b/paramak/parametric_shapes/extruded_mixed_shape.py @@ -89,6 +89,11 @@ def create_solid(self): distance=extrusion_distance, both=self.extrude_both) + # filleting rectangular port cutter edges + # must be done before azimuthal placement + if hasattr(self, "add_fillet"): + solid = self.add_fillet(solid) + solid = self.rotate_solid(solid) cutting_wedge = calculate_wedge_cut(self) solid = self.perform_boolean_operations(solid, wedge_cut=cutting_wedge) diff --git a/tests/test_parametric_components/test_PortCutterCircular.py b/tests/test_parametric_components/test_PortCutterCircular.py index a6d5e7197..49870c4e9 100644 --- a/tests/test_parametric_components/test_PortCutterCircular.py +++ b/tests/test_parametric_components/test_PortCutterCircular.py @@ -1,19 +1,56 @@ - +import math import unittest import paramak +import pytest + + +class TestPortCutterCircular(unittest.TestCase): + + def setUp(self): + self.test_shape = paramak.PortCutterCircular( + distance=300, radius=20 + ) + + def test_default_parameters(self): + """Checks that the default parameters of a PortCutterCircular are correct.""" + + assert self.test_shape.center_point == (0, 0) + assert self.test_shape.workplane == "ZY" + assert self.test_shape.rotation_axis == "Z" + assert self.test_shape.extrusion_start_offset == 1 + assert self.test_shape.stp_filename == "PortCutterCircular.stp" + assert self.test_shape.stl_filename == "PortCutterCircular.stl" + assert self.test_shape.name == "circular_port_cutter" + assert self.test_shape.material_tag == "circular_port_cutter_mat" + + def test_creation(self): + """Creates a circular port cutter using the PortCutterCircular parametric + component and checks that a cadquery solid is created.""" + + assert self.test_shape.solid is not None + assert self.test_shape.volume > 1000 + + def test_relative_volume(self): + """Creates PortCutterCircular shapes and checks that their relative volumes + are correct.""" + + test_volume = self.test_shape.volume + + self.test_shape.extrusion_start_offset = 20 + self.test_shape.azimuth_placement_angle = [0, 90, 180, 270] + + assert self.test_shape.volume == pytest.approx(test_volume * 4) + + def test_absolute_volume(self): + """Creates a PortCutterCircular shape and checks that its volume is correct.""" -# class test_component(unittest.TestCase): -# TODO: fix issue 548 -# def test_creation(self): -# """Checks a PortCutterCircular creation.""" + assert self.test_shape.volume == pytest.approx(math.pi * (20**2) * 300) -# test_component = paramak.PortCutterCircular( -# distance=3, -# z_pos=0.25, -# radius=0.1, -# azimuth_placement_angle=[0, 45, 90, 180] -# ) + self.test_shape.extrusion_start_offset = 20 + self.test_shape.azimuth_placement_angle = [0, 90, 180, 270] + self.test_shape.radius = 10 -# assert test_component.solid is not None + assert self.test_shape.volume == pytest.approx( + math.pi * (10**2) * 300 * 4) diff --git a/tests/test_parametric_components/test_PortCutterRectangular.py b/tests/test_parametric_components/test_PortCutterRectangular.py index 0f927ccc3..7d5c4d374 100644 --- a/tests/test_parametric_components/test_PortCutterRectangular.py +++ b/tests/test_parametric_components/test_PortCutterRectangular.py @@ -3,19 +3,85 @@ import paramak +import pytest + class TestPortCutterRectangular(unittest.TestCase): + def setUp(self): + self.test_shape = paramak.PortCutterRectangular( + width=20, height=40, distance=300 + ) + + def test_default_parameters(self): + """Checks that the default parameters of a PortCutterRectangular are correct.""" + + assert self.test_shape.center_point == (0, 0) + assert self.test_shape.workplane == "ZY" + assert self.test_shape.rotation_axis == "Z" + assert self.test_shape.extrusion_start_offset == 1 + assert self.test_shape.fillet_radius is None + assert self.test_shape.stp_filename == "PortCutterRectangular.stp" + assert self.test_shape.stl_filename == "PortCutterRectangular.stl" + assert self.test_shape.name == "rectangular_port_cutter" + assert self.test_shape.material_tag == "rectangular_port_cutter_mat" + def test_creation(self): - """Checks a PortCutterRectangular creation.""" - - test_component = paramak.PortCutterRectangular( - distance=3, - z_pos=0, - height=0.2, - width=0.4, - fillet_radius=0.02, - azimuth_placement_angle=[0, 45, 90, 180] + """Creates a rectangular port cutter using the PortCutterRectangular parametric + component and checks that a cadquery solid is created.""" + + assert self.test_shape.solid is not None + assert self.test_shape.volume > 1000 + + def test_relative_volume(self): + """Creates PortCutterRectangular shapes and checks that their relative volumes + are correct.""" + + test_volume = self.test_shape.volume + + self.test_shape.extrusion_start_offset = 20 + self.test_shape.azimuth_placement_angle = [0, 90, 180, 270] + + assert self.test_shape.volume == pytest.approx(test_volume * 4) + + def test_absolute_volume(self): + """Creates a PortCutterRectangular shape and checks that its volume is correct.""" + + assert self.test_shape.volume == pytest.approx(20 * 40 * 300) + + self.test_shape.extrusion_start_offset = 20 + self.test_shape.azimuth_placement_angle = [0, 90, 180, 270] + self.test_shape.width = 20 + self.test_shape.height = 20 + + assert self.test_shape.volume == pytest.approx(20 * 20 * 300 * 4) + + def test_workplane(self): + """Creates PortCutterRectangular shapes in different workplanes and checks that + the geometries are correct.""" + + cutting_shape = paramak.RotateStraightShape( + points=[(0, 0), (0, 50), (500, 50), (500, 0)], + workplane="YZ", ) + self.test_shape.cut = cutting_shape + + assert self.test_shape.volume == pytest.approx(20 * 40 * 300 * 0.5) + + self.test_shape.workplane = "XZ" + cutting_shape.workplane = "YZ" + + assert self.test_shape.volume == pytest.approx(20 * 40 * 300 * 0.5) + + def test_filleting(self): + """Creates a PortCutterRectangular shape with filleted edges and checks + that its volume is correct.""" + + test_volume = self.test_shape.volume + self.test_shape.fillet_radius = 5 - assert test_component.solid is not None + assert self.test_shape.volume < test_volume + self.test_shape.workplane = "ZX" + assert self.test_shape.volume < test_volume + self.test_shape.workplane = "YX" + assert self.test_shape.volume < test_volume diff --git a/tests/test_parametric_components/test_VacuumVessel.py b/tests/test_parametric_components/test_VacuumVessel.py index 7026be32d..3499c4d47 100644 --- a/tests/test_parametric_components/test_VacuumVessel.py +++ b/tests/test_parametric_components/test_VacuumVessel.py @@ -22,14 +22,21 @@ def test_ports(self): caquery solid is created.""" cutter1 = paramak.PortCutterRectangular( - distance=3, z_pos=0, height=0.2, width=0.4, fillet_radius=0.01) + distance=3, center_point=( + 0, 0), height=0.2, width=0.4, fillet_radius=0.01) cutter2 = paramak.PortCutterRectangular( - distance=3, z_pos=0.5, height=0.2, width=0.4, fillet_radius=0.00) + distance=3, center_point=( + 0.5, 0), height=0.2, width=0.4, fillet_radius=0.00) cutter3 = paramak.PortCutterRectangular( - distance=3, z_pos=-0.5, height=0.2, width=0.4, + distance=3, center_point=(-0.5, 0), height=0.2, width=0.4, physical_groups=None) cutter4 = paramak.PortCutterCircular( - distance=3, z_pos=0.25, radius=0.1, azimuth_placement_angle=45, + distance=3, + center_point=( + 0.25, + 0), + radius=0.1, + azimuth_placement_angle=45, physical_groups=None) cutter5 = paramak.PortCutterRotated( (0, 0), azimuth_placement_angle=-90, rotation_angle=10,