def load_geometry_from_file_object(
        file: Union[BinaryIO, TextIO],
        extension: str,
        units: str,
        geometry: OFFGeometryNoNexus = 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 = []
        geometry.colors = []
        logging.error("geometry file extension not supported")

    return geometry
def _load_stl_geometry(
        file: Union[BinaryIO, TextIO],
        mult_factor: float,
        geometry: OFFGeometryNoNexus = 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
def load_geometry(
    filename: str,
    units: str,
    geometry: OFFGeometryNoNexus = 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_bin:
            return load_geometry_from_file_object(file_bin, extension, units,
                                                  geometry)
def test_can_set_off_geometry_properties():
    component = Component("test")

    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)
def test_can_get_off_geometry_properties():
    component = Component("test")

    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)
Exemple #6
0
    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, SHAPE_GROUP_NAME)
Exemple #7
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_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
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_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
def _load_off_geometry(
        file: Union[BinaryIO, TextIO],
        mult_factor: float,
        geometry: OFFGeometryNoNexus = 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, face_colors = 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 = faces
    geometry.colors = face_colors
    logging.info("OFF loaded")
    return geometry
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
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
    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():
            geometry = 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,
            )
            if not geometry:
                show_warning_dialog(
                    "3D vector is zero length in cylinder geometry.", "")
        elif self.boxRadioButton.isChecked():
            component.set_box_shape(
                self.boxLengthLineEdit.value(),
                self.boxWidthLineEdit.value(),
                self.boxHeightLineEdit.value(),
                self.unitsLineEdit.text(),
            )
        elif self.meshRadioButton.isChecked() and self.cad_file_name:
            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,
            )
 def create_slit_geometry(self) -> OFFGeometryNoNexus:
     geometry = OFFGeometryNoNexus(self.vertices, self.faces, SHAPE_GROUP_NAME)
     geometry.units = self._units
     return geometry
Exemple #16
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]