예제 #1
0
def test_can_retrieve_list_of_vertices_for_each_face(nexus_wrapper):
    # Reverse process of test_can_record_list_of_vertices_for_each_face

    component = add_component_to_file(nexus_wrapper)

    shape = OFFGeometryNoNexus(
        [QVector3D(0.0, 0.0, 1.0), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0, 0.0, 0.0)],
        [[0, 1, 2]],
    )

    component.set_off_shape(shape)

    test_input_flat_list_of_vertex_indices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    # Define there are three faces, the difference in starting index indicates there are three vertices
    # in the first face (triangle), four in the second (square), and five in the third face (pentagon)
    test_input_start_index_of_each_face = [0, 3, 7]

    expected_output_vertex_indices_split_by_face = [
        [0, 1, 2],
        [3, 4, 5, 6],
        [7, 8, 9, 10, 11],
    ]

    nexus_shape, _ = component.shape

    nexus_wrapper.set_field_value(
        nexus_shape.group, "winding_order", test_input_flat_list_of_vertex_indices
    )
    nexus_wrapper.set_field_value(
        nexus_shape.group, "faces", test_input_start_index_of_each_face
    )

    assert nexus_shape.faces == expected_output_vertex_indices_split_by_face
def _load_stl_geometry(
    file: StringIO,
    mult_factor: float,
    geometry: OFFGeometry = OFFGeometryNoNexus()
) -> OFFGeometry:
    """
    Loads geometry from an STL file into an OFFGeometry instance.

    :param file: The file containing an STL geometry.
    :param mult_factor: The multiplication factor for unit conversion.
    :param geometry: The optional OFFGeometry to load the STL data into. If not provided, a new instance will be
    returned.
    :return: An OFFGeometry instance containing that file's geometry.
    """
    mesh_data = mesh.Mesh.from_file("", fh=file, calculate_normals=False)
    # numpy-stl loads numbers as python decimals, not floats, which aren't valid in json
    geometry.vertices = [
        QVector3D(
            float(corner[0]) * mult_factor,
            float(corner[1]) * mult_factor,
            float(corner[2]) * mult_factor,
        ) for triangle in mesh_data.vectors for corner in triangle
    ]
    geometry.faces = [[i * 3, (i * 3) + 1, (i * 3) + 2]
                      for i in range(len(mesh_data.vectors))]
    logging.info("STL loaded")
    return geometry
예제 #3
0
def test_can_set_off_geometry_properties(nexus_wrapper):
    component = add_component_to_file(nexus_wrapper)

    vertices = [
        QVector3D(0.0, 0.0, 1.0),
        QVector3D(0.0, 1.0, 0.0),
        QVector3D(0.0, 0.0, 0.0),
        QVector3D(0.0, 1.0, 1.0),
    ]

    faces = [[0, 1, 2, 3]]

    shape = OFFGeometryNoNexus(vertices, faces)

    component.set_off_shape(shape)

    nexus_shape, _ = component.shape

    vertex_2_x = 0.5
    vertex_2_y = -0.5
    vertex_2_z = 0
    new_vertices = [
        QVector3D(-0.5, -0.5, 0),
        QVector3D(0, 0.5, 0),
        QVector3D(vertex_2_x, vertex_2_y, vertex_2_z),
    ]
    triangle = [0, 1, 2]
    new_faces = [triangle]
    nexus_shape.vertices = new_vertices
    nexus_shape.faces = new_faces

    assert nexus_shape.faces == new_faces
    assert nexus_shape.vertices[2].x() == approx(vertex_2_x)
    assert nexus_shape.vertices[2].y() == approx(vertex_2_y)
    assert nexus_shape.vertices[2].z() == approx(vertex_2_z)
예제 #4
0
def test_can_record_list_of_vertices_for_each_face(nexus_wrapper):
    # Reverse process of test_can_retrieve_list_of_vertices_for_each_face
    component = add_component_to_file(nexus_wrapper)

    shape = OFFGeometryNoNexus(
        [QVector3D(0.0, 0.0, 1.0), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0, 0.0, 0.0)],
        [[0, 1, 2]],
    )

    component.set_off_shape(shape)
    nexus_shape, _ = component.shape

    test_input_vertex_indices_split_by_face = [
        [0, 1, 2],
        [3, 4, 5, 6],
        [7, 8, 9, 10, 11],
    ]

    record_faces_in_file(
        nexus_wrapper, nexus_shape.group, test_input_vertex_indices_split_by_face
    )

    expected_output_flat_list_of_vertex_indices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    expected_output_start_index_of_each_face = [0, 3, 7]

    flat_list_of_vertex_indices = nexus_shape.group["winding_order"][...].tolist()
    start_index_of_each_face = nexus_shape.group["faces"][...].tolist()

    assert flat_list_of_vertex_indices == expected_output_flat_list_of_vertex_indices
    assert start_index_of_each_face == expected_output_start_index_of_each_face
def load_geometry_from_file_object(
        file: StringIO,
        extension: str,
        units: str,
        geometry: OFFGeometry = OFFGeometryNoNexus(),
) -> OFFGeometry:
    """
    Loads geometry from a file object into an OFFGeometry instance

    Supported file types are OFF and STL.

    :param file: The file object to load the geometry from.
    :param units: A unit of length in the form of a string. Used to determine the multiplication factor.
    :param geometry: The optional OFFGeometry to load the geometry data into. If not provided, a new instance will be
    returned.
    :return: An OFFGeometry instance containing that file's geometry, or an empty instance if filename's extension is
    unsupported.
    """

    mult_factor = calculate_unit_conversion_factor(units, METRES)

    if extension == ".off":
        _load_off_geometry(file, mult_factor, geometry)
    elif extension == ".stl":
        _load_stl_geometry(file, mult_factor, geometry)
    else:
        geometry.faces = []
        geometry.vertices = []
        logging.error("geometry file extension not supported")

    return geometry
예제 #6
0
def test_can_get_off_geometry_properties(nexus_wrapper):
    component = add_component_to_file(nexus_wrapper)

    vertex_3_x = 0.0
    vertex_3_y = 1.0
    vertex_3_z = 1.0

    vertices = [
        QVector3D(0, 0, 1),
        QVector3D(0, 1, 0),
        QVector3D(0, 0, 0),
        QVector3D(vertex_3_x, vertex_3_y, vertex_3_z),
    ]

    faces = [[0, 1, 2, 3]]

    shape = OFFGeometryNoNexus(vertices, faces)

    component.set_off_shape(shape)

    nexus_shape, _ = component.shape
    assert isinstance(nexus_shape, OFFGeometryNexus)
    assert nexus_shape.faces == faces
    assert nexus_shape.vertices[3].x() == approx(vertex_3_x)
    assert nexus_shape.vertices[3].y() == approx(vertex_3_y)
    assert nexus_shape.vertices[3].z() == approx(vertex_3_z)
예제 #7
0
def test_GIVEN_pixel_grid_WHEN_setting_off_geometry_shape_THEN_off_geometry_is_not_called_with_pixel_data(
    component,
):
    pixel_grid = PixelGrid()
    off_geometry = OFFGeometryNoNexus(vertices=[], faces=[])
    units = "m"
    filename = "somefile.off"

    with patch(
        "nexus_constructor.component.component.OFFGeometryNexus"
    ) as mock_off_geometry_constructor:

        component.set_off_shape(
            loaded_geometry=off_geometry,
            units=units,
            filename=filename,
            pixel_data=pixel_grid,
        )
        mock_off_geometry_constructor.assert_called_once_with(
            component.file,
            component.group[PIXEL_SHAPE_GROUP_NAME],
            units,
            filename,
            None,
        )
def load_geometry(
    filename: str, units: str,
    geometry: OFFGeometry = OFFGeometryNoNexus()) -> OFFGeometry:
    """
    Loads geometry from a file into an OFFGeometry instance

    Supported file types are OFF and STL.
    :param filename: The name of the file to open.
    :param units: A unit of length in the form of a string. Used to determine the multiplication factor.
    :param geometry: The optional OFFGeometry to load the geometry data into. If not provided, a new instance will be
    returned.
    :return: An OFFGeometry instance containing that file's geometry, or an empty instance if filename's extension is
    unsupported.
    """

    extension = filename[filename.rfind("."):].lower()

    try:
        with open(filename) as file:
            return load_geometry_from_file_object(file, extension, units,
                                                  geometry)
    except UnicodeDecodeError:
        # Try again in case the file is in binary. At least one of these should work when a user selects a file because
        # GeometryFileValidator inspects the file beforehand to check that it's valid.
        with open(filename, "rb") as file:
            return load_geometry_from_file_object(file, extension, units,
                                                  geometry)
    def create_disk_chopper_geometry(self) -> OFFGeometryNoNexus:
        """
        Create the string that stores all the information needed in the OFF file.
        """
        self.convert_chopper_details_to_off()

        # Add the point information to the string
        vertices = [point.point_to_qvector3d() for point in self.points]

        return OFFGeometryNoNexus(vertices, self.faces)
예제 #10
0
def test_GIVEN_off_properties_WHEN_setting_off_geometry_shape_THEN_shape_group_has_class_nxoff_geometry(
    component,
):
    off_geometry = OFFGeometryNoNexus(vertices=[], faces=[])

    with patch("nexus_constructor.component.component.OFFGeometryNexus"):
        component.set_off_shape(loaded_geometry=off_geometry)

    assert (
        component.group[SHAPE_GROUP_NAME].attrs["NX_class"] == OFF_GEOMETRY_NEXUS_NAME
    )
예제 #11
0
def get_dummy_OFF():
    # A square with a triangle on the side
    original_vertices = [
        QVector3D(0, 0, 0),
        QVector3D(0, 1, 0),
        QVector3D(1, 1, 0),
        QVector3D(1, 0, 0),
        QVector3D(1.5, 0.5, 0),
    ]
    original_faces = [[0, 1, 2, 3], [2, 3, 4]]

    return OFFGeometryNoNexus(vertices=original_vertices, faces=original_faces)
def test_GIVEN_geometry_WHEN_creating_off_mesh_THEN_geometry_contains_original_geometry(
):
    off_output = OFFGeometryNoNexus(
        vertices=[QVector3D(0, 0, 0),
                  QVector3D(0, 1, 0),
                  QVector3D(1, 1, 0)],
        faces=[[0, 1, 2]],
    )

    off_mesh = OffMesh(off_output, None)

    assert off_mesh.geometry().vertex_count == VERTICES_IN_TRIANGLE
def test_GIVEN_a_triangle_WHEN_creating_off_geometry_with_no_pixel_data_THEN_vertex_count_equals_3(
):
    off_geometry = OFFGeometryNoNexus(
        vertices=[QVector3D(0, 0, 0),
                  QVector3D(0, 1, 0),
                  QVector3D(1, 1, 0)],
        faces=[[0, 1, 2]],
    )

    qt_geometry = QtOFFGeometry(off_geometry, None)

    assert qt_geometry.vertex_count == 3
예제 #14
0
def test_GIVEN_faces_WHEN_calling_winding_order_on_OFF_THEN_order_is_correct():
    vertices = [
        QVector3D(0, 0, 1),
        QVector3D(0, 1, 0),
        QVector3D(0, 0, 0),
        QVector3D(0, 1, 1),
    ]

    faces = [[0, 1, 2, 3]]

    geom = OFFGeometryNoNexus(vertices, faces)
    expected = [point for face in faces for point in face]

    assert expected == geom.winding_order
예제 #15
0
def test_GIVEN_off_geometry_WHEN_calling_off_geometry_on_offGeometry_THEN_original_geometry_is_returned():
    vertices = [
        QVector3D(0, 0, 1),
        QVector3D(0, 1, 0),
        QVector3D(0, 0, 0),
        QVector3D(0, 1, 1),
    ]

    faces = [[0, 1, 2, 3]]
    geom = OFFGeometryNoNexus(vertices, faces)

    assert geom.faces == faces
    assert geom.vertices == vertices
    assert geom.off_geometry == geom
예제 #16
0
def test_GIVEN_faces_WHEN_calling_winding_order_indices_on_OFF_THEN_order_is_correct():
    vertices = [
        QVector3D(0, 0, 1),
        QVector3D(0, 1, 0),
        QVector3D(0, 0, 0),
        QVector3D(0, 1, 1),
    ]

    faces = [[0, 1, 2, 3]]

    geom = OFFGeometryNoNexus(vertices, faces)

    expected = [0]  # only one face

    assert expected == geom.winding_order_indices
예제 #17
0
def test_can_override_existing_shape(nexus_wrapper):
    component = add_component_to_file(nexus_wrapper, "some_field", 42, "component_name")

    component.set_cylinder_shape()
    cylinder, _ = component.shape
    assert isinstance(
        cylinder, CylindricalGeometry
    ), "Expect shape to initially be a cylinder"

    vertices = [QVector3D(-0.5, -0.5, 0), QVector3D(0, 0.5, 0), QVector3D(0.5, -0.5, 0)]
    faces = [[0, 1, 2]]
    input_mesh = OFFGeometryNoNexus(vertices, faces)
    component.set_off_shape(input_mesh)
    output_mesh, _ = component.shape
    assert isinstance(output_mesh, OFFGeometryNexus), "Expect shape to now be a mesh"
예제 #18
0
def test_GIVEN_component_with_off_shape_information_WHEN_duplicating_component_THEN_shape_information_is_stored_in_nexus_file(
):
    wrapper = NexusWrapper("test_duplicate_off_shape")
    instrument = Instrument(wrapper, NX_CLASS_DEFINITIONS)

    first_component_name = "component1"
    first_component_nx_class = "NXdetector"
    description = "desc"
    first_component = instrument.create_component(first_component_name,
                                                  first_component_nx_class,
                                                  description)

    vertices = [
        QVector3D(-0.5, -0.5, 0.5),
        QVector3D(0.5, -0.5, 0.5),
        QVector3D(-0.5, 0.5, 0.5),
        QVector3D(0.5, 0.5, 0.5),
        QVector3D(-0.5, 0.5, -0.5),
        QVector3D(0.5, 0.5, -0.5),
        QVector3D(-0.5, -0.5, -0.5),
        QVector3D(0.5, -0.5, -0.5),
    ]

    faces = [
        [0, 1, 3, 2],
        [2, 3, 5, 4],
        [4, 5, 7, 6],
        [6, 7, 1, 0],
        [1, 7, 5, 3],
        [6, 0, 2, 4],
    ]

    first_component.set_off_shape(
        OFFGeometryNoNexus(vertices=vertices, faces=faces))

    tree_model = ComponentTreeModel(instrument)

    first_component_index = tree_model.index(0, 0, QModelIndex())
    tree_model.duplicate_node(first_component_index)

    assert tree_model.rowCount(QModelIndex()) == 3
    second_component_index = tree_model.index(2, 0, QModelIndex())
    second_component = second_component_index.internalPointer()
    second_shape, _ = second_component.shape

    assert second_shape.vertices == vertices
    assert second_shape.faces == faces
예제 #19
0
def test_can_get_cad_file_units_from_model_when_already_in_model(nexus_wrapper):
    component = add_component_to_file(nexus_wrapper, "some_field", 42, "component_name")
    vertices = [
        QVector3D(-0.5, -0.5, 0),
        QVector3D(0, 0.5, 0),
        QVector3D(0.5, -0.05, 0),
    ]
    triangle = [0, 1, 2]
    faces = [triangle]
    input_mesh = OFFGeometryNoNexus(vertices, faces)
    component.set_off_shape(input_mesh)

    units = "m"
    component.group["shape"]["cad_file_units"] = units

    output_mesh, _ = component.shape
    assert isinstance(output_mesh, OFFGeometryNexus)
    assert output_mesh.units == units
    assert output_mesh.group["cad_file_units"][()] == units
예제 #20
0
def test_setting_cad_path_through_shape_persists_in_file(nexus_wrapper):
    component = add_component_to_file(nexus_wrapper, "some_field", 42, "component_name")

    vertex_2_x = 0.5
    vertex_2_y = -0.5
    vertex_2_z = 0
    vertices = [
        QVector3D(-0.5, -0.5, 0),
        QVector3D(0, 0.5, 0),
        QVector3D(vertex_2_x, vertex_2_y, vertex_2_z),
    ]
    triangle = [0, 1, 2]
    faces = [triangle]
    input_mesh = OFFGeometryNoNexus(vertices, faces)
    component.set_off_shape(input_mesh)
    filepath = "/home/asdf/teapot.off"

    output_mesh, _ = component.shape
    output_mesh.file_path = filepath
    assert component.group["shape"]["cad_file_path"][()] == filepath
예제 #21
0
def test_no_cad_path_returns_none_when_getting_cad_path_from_off_geometry(
    nexus_wrapper,
):
    component = add_component_to_file(nexus_wrapper, "some_field", 42, "component_name")

    vertex_2_x = 0.5
    vertex_2_y = -0.5
    vertex_2_z = 0
    vertices = [
        QVector3D(-0.5, -0.5, 0),
        QVector3D(0, 0.5, 0),
        QVector3D(vertex_2_x, vertex_2_y, vertex_2_z),
    ]
    triangle = [0, 1, 2]
    faces = [triangle]
    input_mesh = OFFGeometryNoNexus(vertices, faces)
    component.set_off_shape(input_mesh)

    output_mesh, _ = component.shape
    assert output_mesh.file_path is None
    def generate_geometry_model(
        self, component: Component, pixel_data: PixelData = None
    ):
        """
        Generates a geometry model depending on the type of geometry selected and the current values
        of the line edits that apply to the particular geometry type.
        :return: The generated model.
        """
        if self.CylinderRadioButton.isChecked():

            component.set_cylinder_shape(
                QVector3D(
                    self.cylinderXLineEdit.value(),
                    self.cylinderYLineEdit.value(),
                    self.cylinderZLineEdit.value(),
                ),
                self.cylinderHeightLineEdit.value(),
                self.cylinderRadiusLineEdit.value(),
                self.unitsLineEdit.text(),
                pixel_data=pixel_data,
            )
        elif self.meshRadioButton.isChecked():
            mesh_geometry = OFFGeometryNoNexus()
            geometry_model = load_geometry(
                self.cad_file_name, self.unitsLineEdit.text(), mesh_geometry
            )

            # Units have already been used during loading the file, but we store them and file name
            # so we can repopulate their fields in the edit component window
            geometry_model.units = self.unitsLineEdit.text()
            geometry_model.file_path = self.cad_file_name

            component.set_off_shape(
                geometry_model,
                units=self.unitsLineEdit.text(),
                filename=self.fileLineEdit.text(),
                pixel_data=pixel_data,
            )
예제 #23
0
def test_can_add_mesh_shape_to_and_component_and_get_the_same_shape_back(nexus_wrapper):
    component = add_component_to_file(nexus_wrapper, "some_field", 42, "component_name")

    # Our test input mesh is a single triangle
    vertex_2_x = 0.5
    vertex_2_y = -0.5
    vertex_2_z = 0
    vertices = [
        QVector3D(-0.5, -0.5, 0),
        QVector3D(0, 0.5, 0),
        QVector3D(vertex_2_x, vertex_2_y, vertex_2_z),
    ]
    triangle = [0, 1, 2]
    faces = [triangle]
    input_mesh = OFFGeometryNoNexus(vertices, faces)
    component.set_off_shape(input_mesh)

    output_mesh, _ = component.shape
    assert isinstance(output_mesh, OFFGeometryNexus)
    assert output_mesh.faces[0] == triangle
    assert output_mesh.vertices[2].x() == approx(vertex_2_x)
    assert output_mesh.vertices[2].y() == approx(vertex_2_y)
    assert output_mesh.vertices[2].z() == approx(vertex_2_z)
def _load_off_geometry(
    file: StringIO,
    mult_factor: float,
    geometry: OFFGeometry = OFFGeometryNoNexus()
) -> OFFGeometry:
    """
    Loads geometry from an OFF file into an OFFGeometry instance.

    :param file: The file containing an OFF geometry.
    :param mult_factor: The multiplication factor for unit conversion.
    :param geometry: The optional OFFGeometry to load the OFF data into. If not provided, a new instance will be
    returned.
    :return: An OFFGeometry instance containing that file's geometry.
    """
    vertices, faces = parse_off_file(file)

    geometry.vertices = [
        QVector3D(x * mult_factor, y * mult_factor, z * mult_factor)
        for x, y, z in (vertex for vertex in vertices)
    ]
    geometry.faces = [face.tolist()[1:] for face in faces]
    logging.info("OFF loaded")
    return geometry
예제 #25
0
def test_GIVEN_nothing_WHEN_constructing_OFFGeometry_THEN_geometry_str_is_OFF():
    geom = OFFGeometryNoNexus()
    assert geom.geometry_str == "OFF"
예제 #26
0
def test_GIVEN_off_file_containing_geometry_WHEN_loading_geometry_to_file_THEN_vertices_and_faces_loaded_are_the_same_as_the_file(
):
    model = OFFGeometryNoNexus()
    model.units = "m"

    off_file = ("OFF\n"
                "#  cube.off\n"
                "#  A cube\n"
                "8 6 0\n"
                "-0.500000 -0.500000 0.500000\n"
                "0.500000 -0.500000 0.500000\n"
                "-0.500000 0.500000 0.500000\n"
                "0.500000 0.500000 0.500000\n"
                "-0.500000 0.500000 -0.500000\n"
                "0.500000 0.500000 -0.500000\n"
                "-0.500000 -0.500000 -0.500000\n"
                "0.500000 -0.500000 -0.500000\n"
                "4 0 1 3 2\n"
                "4 2 3 5 4\n"
                "4 4 5 7 6\n"
                "4 6 7 1 0\n"
                "4 1 7 5 3\n"
                "4 6 0 2 4\n")

    load_geometry_from_file_object(StringIO(off_file), ".off", model.units,
                                   model)

    assert model.vertices == [
        QVector3D(-0.5, -0.5, 0.5),
        QVector3D(0.5, -0.5, 0.5),
        QVector3D(-0.5, 0.5, 0.5),
        QVector3D(0.5, 0.5, 0.5),
        QVector3D(-0.5, 0.5, -0.5),
        QVector3D(0.5, 0.5, -0.5),
        QVector3D(-0.5, -0.5, -0.5),
        QVector3D(0.5, -0.5, -0.5),
    ]
    assert model.faces == [
        [0, 1, 3, 2],
        [2, 3, 5, 4],
        [4, 5, 7, 6],
        [6, 7, 1, 0],
        [1, 7, 5, 3],
        [6, 0, 2, 4],
    ]
    assert model.winding_order == [
        0,
        1,
        3,
        2,
        2,
        3,
        5,
        4,
        4,
        5,
        7,
        6,
        6,
        7,
        1,
        0,
        1,
        7,
        5,
        3,
        6,
        0,
        2,
        4,
    ]
    assert model.winding_order_indices == [0, 4, 8, 12, 16, 20]