Skip to content

2D/3D view

Class documentation

Bases: QWidget

A class representing a spatial view widget.

Attributes:

Name Type Description
graphWidget PlotWidget

A PlotWidget for 2D plots.

fig Figure

A Figure object for 3D plots.

canvas FigureCanvasQTAgg

A FigureCanvas for displaying the 3D plot.

axes Axes3D

Axes for 3D plot.

prm PRM

An instance of PRM class.

a_ref float

Reference angle in degrees

home_pos dict

Dictionary containing home position coordinates.

ref_circle ndarray

Array containing points of reference circle.

m2 ndarray

Array containing points of mirror 2 projected onto the wall of the observatory

m1 ndarray

Array containing points of mirror 1 turned and projected onto the wall of the observatory

Source code in GUI.py
class SpatialView(QWidget):
    """
    A class representing a spatial view widget.

    Attributes:
        graphWidget (PlotWidget): A PlotWidget for 2D plots.
        fig (Figure): A Figure object for 3D plots.
        canvas (FigureCanvas): A FigureCanvas for displaying the 3D plot.
        axes (Axes3D): Axes for 3D plot.
        prm (PRM): An instance of PRM class.
        a_ref (float): Reference angle in degrees
        home_pos (dict): Dictionary containing home position coordinates.
        ref_circle (numpy.ndarray): Array containing points of reference circle.
        m2 (numpy.ndarray): Array containing points of mirror 2 projected onto the wall of the observatory
        m1 (numpy.ndarray): Array containing points of mirror 1 turned and projected onto the wall of the observatory
    """

    def __init__(self):
        """
        Initialize the SpatialView widget.

        Loads the UI file, initializes 2D and 3D plots, and sets up initial parameters.
        """
        super(SpatialView, self).__init__()
        loadUi("ui_files/SpatialView.ui", self)
        self.findChild(QWidget, "spatial_view")


        #Initializing 2D plot
        self.graphWidget = pg.PlotWidget()
        self.two_dim.addWidget(self.graphWidget)

        pen = pg.mkPen(color=(0, 255, 0))
        self.data_line_ref_circle= self.graphWidget.plot([], [], pen=pen)
        pen = pg.mkPen(color=(0, 0, 255))
        self.data_line_m2_circle= self.graphWidget.plot([], [], pen=pen)
        pen = pg.mkPen(color=(255, 0, 0))
        self.data_line_m1_circle= self.graphWidget.plot([], [], pen=pen)

        self.graphWidget.setBackground("w")
        self.graphWidget.setLabel("left", "<span style=\"color:black;font-size:11px\">z - coordinate tangens</span>")
        self.graphWidget.setLabel("bottom", "<span style=\"color:black;font-size:11px\">y - coordinate tangens</span>")
        self.graphWidget.showGrid(x=True, y=True)
        self.graphWidget.setAspectLocked(lock=True)

        #Initializing 3D plot
        self.fig = Figure()
        self.canvas = FigureCanvas(self.fig)
        self.axes = self.fig.add_subplot(111, projection='3d')
        self.three_dim.addWidget(self.canvas)
        self.axes.set_xlim(8000,9000)


        self.prm = PRM()
        self.a_ref = 2.5




        self.axes.scatter(self.prm.P1c[0],self.prm.P1c[1],self.prm.P1c[2],color='b', marker='o')
        self.axes.scatter(self.prm.P2c[0],self.prm.P2c[1],self.prm.P2c[2], color='b', marker='o')

        self.home_pos = {'E':63, 'A':180}
        self.prm.update_position(self.home_pos)

        #2D Plots
        self.ref_circle = self.prm.circle_points([0,0,0],[0,0,1], np.tan(np.deg2rad(self.a_ref)), 100)
        x_coords = self.ref_circle[:, 0][1:]
        y_coords = self.ref_circle[:, 1][1:]
        self.data_line_ref_circle.setData(x_coords, y_coords)


        self.m2 = self.prm.project_m2()
        x_coords = -(self.m2[:, 0])
        y_coords = self.m2[:, 2]
        self.data_line_m2_circle.setData(x_coords, y_coords)

        self.m1 = self.prm.project_m1(self.home_pos)
        x_coords = -(self.m1[:, 0])
        y_coords = self.m1[:, 2]
        self.data_line_m1_circle.setData(x_coords, y_coords)

    def plot_vector(self,p1,p2):
        """
        Plot a vector in the 3D plot.

        Args:
            p1 (numpy.ndarray): Starting point of the vector.
            p2 (numpy.ndarray): Ending point of the vector.
        """
        p2 = p2-p1
        self.axes.quiver(p1[0], p1[1], p1[2],p2[0], p2[1], p2[2], normalize=False)
    def plot_line(self, p1, p2, color):
        """
        Plot a line in the 3D plot.

        Args:
            p1 (numpy.ndarray): Starting point of the line.
            p2 (numpy.ndarray): Ending point of the line.
            color (str): Color of the line.
        """
        point1 = p1
        point2 = p2
        self.axes.plot([point1[0], point2[0]], [point1[1], point2[1]], [point1[2], point2[2]], color=color, label='Line')
    def plot_mirror(self, P, n, r, nn,color):
        """
        Plot a mirror in the 3D plot.

        Args:
            P (numpy.ndarray): Center point of the mirror.
            n (numpy.ndarray): Normal vector of the mirror.
            r (float): Radius of the mirror.
            nn (int): Number of points to generate on the mirror.
            color (str): Color of the mirror.
        """
        Cp = self.prm.circle_points(P,n, r, nn)
        x = np.array(Cp[:, 0])[1:]
        y = np.array(Cp[:, 1])[1:]
        z = np.array(Cp[:, 2])[1:]
        self.axes.plot(x,y,z, color = color)


    def set_axes_equal(self,ax):
        """
        Make axes of 3D plot have equal scale so that spheres appear as spheres,
        cubes as cubes, etc.

        Input
        ax: a matplotlib axis, e.g., as output from plt.gca().
        """

        x_limits = ax.get_xlim3d()
        y_limits = ax.get_ylim3d()
        z_limits = ax.get_zlim3d()

        x_range = abs(x_limits[1] - x_limits[0])
        x_middle = np.mean(x_limits)
        y_range = abs(y_limits[1] - y_limits[0])
        y_middle = np.mean(y_limits)
        z_range = abs(z_limits[1] - z_limits[0])
        z_middle = np.mean(z_limits)

        # The plot bounding box is a sphere in the sense of the infinity
        # norm, hence I call half the max range the plot radius.
        plot_radius = 0.5*max([x_range, y_range, z_range])

        ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
        ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
        ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius]) 

    def update_view_plot(self,pos):
        """
        Update the view plot based on the new position.

        Args:
            pos (dict): Dictionary containing the new position coordinates.

        This method updates the position of the mirrors and other elements in the view plot based on the new position.
        It clears the 3D plot, plots mirrors, normal vectors, vectors from the sun, and lines from M1 to M2 and to the house.
        It also sets the axes equal for better visualization.

        Additionally, it updates the 2D plot by projecting M1.

        """
        self.prm.update_position(pos)

        #Updating 3D plot
        self.axes.cla()
        #Plotting the Mirrors
        self.plot_mirror(self.prm.P2c,self.prm.n_p2,self.prm.m2r, self.prm.nn, 'b')
        self.plot_mirror(self.prm.P1c,self.prm.n_p1,self.prm.m2r, self.prm.nn, 'b')
        #Plotting normalvector of mirror 2
        self.plot_line(self.prm.P2c, self.prm.P2c+self.prm.n_p2*1000, 'c')
        #Plotting vector from sun to mirror 1
        self.plot_line(self.prm.P1c, self.prm.P1c+self.prm.V_sun*1000, 'r')
        #Plotting normalvector of mirror 1
        self.plot_line(self.prm.P1c, self.prm.P1c+self.prm.n_p1*1000, 'c')
        #Plotting lines from M1 to M2 and to the house
        self.plot_line(self.prm.P1c,self.prm.P2c,'r')
        self.plot_line(self.prm.PV,self.prm.P2c, 'r')
        self.set_axes_equal(self.axes)

        #Updating 2D plot
        self.m1 = self.prm.project_m1(self.home_pos)
        x_coords = -(self.m1[:, 0])
        y_coords = self.m1[:, 2]
        self.data_line_m1_circle.setData(x_coords, y_coords)

__init__()

Initialize the SpatialView widget.

Loads the UI file, initializes 2D and 3D plots, and sets up initial parameters.

Source code in GUI.py
def __init__(self):
    """
    Initialize the SpatialView widget.

    Loads the UI file, initializes 2D and 3D plots, and sets up initial parameters.
    """
    super(SpatialView, self).__init__()
    loadUi("ui_files/SpatialView.ui", self)
    self.findChild(QWidget, "spatial_view")


    #Initializing 2D plot
    self.graphWidget = pg.PlotWidget()
    self.two_dim.addWidget(self.graphWidget)

    pen = pg.mkPen(color=(0, 255, 0))
    self.data_line_ref_circle= self.graphWidget.plot([], [], pen=pen)
    pen = pg.mkPen(color=(0, 0, 255))
    self.data_line_m2_circle= self.graphWidget.plot([], [], pen=pen)
    pen = pg.mkPen(color=(255, 0, 0))
    self.data_line_m1_circle= self.graphWidget.plot([], [], pen=pen)

    self.graphWidget.setBackground("w")
    self.graphWidget.setLabel("left", "<span style=\"color:black;font-size:11px\">z - coordinate tangens</span>")
    self.graphWidget.setLabel("bottom", "<span style=\"color:black;font-size:11px\">y - coordinate tangens</span>")
    self.graphWidget.showGrid(x=True, y=True)
    self.graphWidget.setAspectLocked(lock=True)

    #Initializing 3D plot
    self.fig = Figure()
    self.canvas = FigureCanvas(self.fig)
    self.axes = self.fig.add_subplot(111, projection='3d')
    self.three_dim.addWidget(self.canvas)
    self.axes.set_xlim(8000,9000)


    self.prm = PRM()
    self.a_ref = 2.5




    self.axes.scatter(self.prm.P1c[0],self.prm.P1c[1],self.prm.P1c[2],color='b', marker='o')
    self.axes.scatter(self.prm.P2c[0],self.prm.P2c[1],self.prm.P2c[2], color='b', marker='o')

    self.home_pos = {'E':63, 'A':180}
    self.prm.update_position(self.home_pos)

    #2D Plots
    self.ref_circle = self.prm.circle_points([0,0,0],[0,0,1], np.tan(np.deg2rad(self.a_ref)), 100)
    x_coords = self.ref_circle[:, 0][1:]
    y_coords = self.ref_circle[:, 1][1:]
    self.data_line_ref_circle.setData(x_coords, y_coords)


    self.m2 = self.prm.project_m2()
    x_coords = -(self.m2[:, 0])
    y_coords = self.m2[:, 2]
    self.data_line_m2_circle.setData(x_coords, y_coords)

    self.m1 = self.prm.project_m1(self.home_pos)
    x_coords = -(self.m1[:, 0])
    y_coords = self.m1[:, 2]
    self.data_line_m1_circle.setData(x_coords, y_coords)

plot_line(p1, p2, color)

Plot a line in the 3D plot.

Parameters:

Name Type Description Default
p1 ndarray

Starting point of the line.

required
p2 ndarray

Ending point of the line.

required
color str

Color of the line.

required
Source code in GUI.py
def plot_line(self, p1, p2, color):
    """
    Plot a line in the 3D plot.

    Args:
        p1 (numpy.ndarray): Starting point of the line.
        p2 (numpy.ndarray): Ending point of the line.
        color (str): Color of the line.
    """
    point1 = p1
    point2 = p2
    self.axes.plot([point1[0], point2[0]], [point1[1], point2[1]], [point1[2], point2[2]], color=color, label='Line')

plot_mirror(P, n, r, nn, color)

Plot a mirror in the 3D plot.

Parameters:

Name Type Description Default
P ndarray

Center point of the mirror.

required
n ndarray

Normal vector of the mirror.

required
r float

Radius of the mirror.

required
nn int

Number of points to generate on the mirror.

required
color str

Color of the mirror.

required
Source code in GUI.py
def plot_mirror(self, P, n, r, nn,color):
    """
    Plot a mirror in the 3D plot.

    Args:
        P (numpy.ndarray): Center point of the mirror.
        n (numpy.ndarray): Normal vector of the mirror.
        r (float): Radius of the mirror.
        nn (int): Number of points to generate on the mirror.
        color (str): Color of the mirror.
    """
    Cp = self.prm.circle_points(P,n, r, nn)
    x = np.array(Cp[:, 0])[1:]
    y = np.array(Cp[:, 1])[1:]
    z = np.array(Cp[:, 2])[1:]
    self.axes.plot(x,y,z, color = color)

plot_vector(p1, p2)

Plot a vector in the 3D plot.

Parameters:

Name Type Description Default
p1 ndarray

Starting point of the vector.

required
p2 ndarray

Ending point of the vector.

required
Source code in GUI.py
def plot_vector(self,p1,p2):
    """
    Plot a vector in the 3D plot.

    Args:
        p1 (numpy.ndarray): Starting point of the vector.
        p2 (numpy.ndarray): Ending point of the vector.
    """
    p2 = p2-p1
    self.axes.quiver(p1[0], p1[1], p1[2],p2[0], p2[1], p2[2], normalize=False)

set_axes_equal(ax)

Make axes of 3D plot have equal scale so that spheres appear as spheres, cubes as cubes, etc.

Input ax: a matplotlib axis, e.g., as output from plt.gca().

Source code in GUI.py
def set_axes_equal(self,ax):
    """
    Make axes of 3D plot have equal scale so that spheres appear as spheres,
    cubes as cubes, etc.

    Input
    ax: a matplotlib axis, e.g., as output from plt.gca().
    """

    x_limits = ax.get_xlim3d()
    y_limits = ax.get_ylim3d()
    z_limits = ax.get_zlim3d()

    x_range = abs(x_limits[1] - x_limits[0])
    x_middle = np.mean(x_limits)
    y_range = abs(y_limits[1] - y_limits[0])
    y_middle = np.mean(y_limits)
    z_range = abs(z_limits[1] - z_limits[0])
    z_middle = np.mean(z_limits)

    # The plot bounding box is a sphere in the sense of the infinity
    # norm, hence I call half the max range the plot radius.
    plot_radius = 0.5*max([x_range, y_range, z_range])

    ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
    ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
    ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius]) 

update_view_plot(pos)

Update the view plot based on the new position.

Parameters:

Name Type Description Default
pos dict

Dictionary containing the new position coordinates.

required

This method updates the position of the mirrors and other elements in the view plot based on the new position. It clears the 3D plot, plots mirrors, normal vectors, vectors from the sun, and lines from M1 to M2 and to the house. It also sets the axes equal for better visualization.

Additionally, it updates the 2D plot by projecting M1.

Source code in GUI.py
def update_view_plot(self,pos):
    """
    Update the view plot based on the new position.

    Args:
        pos (dict): Dictionary containing the new position coordinates.

    This method updates the position of the mirrors and other elements in the view plot based on the new position.
    It clears the 3D plot, plots mirrors, normal vectors, vectors from the sun, and lines from M1 to M2 and to the house.
    It also sets the axes equal for better visualization.

    Additionally, it updates the 2D plot by projecting M1.

    """
    self.prm.update_position(pos)

    #Updating 3D plot
    self.axes.cla()
    #Plotting the Mirrors
    self.plot_mirror(self.prm.P2c,self.prm.n_p2,self.prm.m2r, self.prm.nn, 'b')
    self.plot_mirror(self.prm.P1c,self.prm.n_p1,self.prm.m2r, self.prm.nn, 'b')
    #Plotting normalvector of mirror 2
    self.plot_line(self.prm.P2c, self.prm.P2c+self.prm.n_p2*1000, 'c')
    #Plotting vector from sun to mirror 1
    self.plot_line(self.prm.P1c, self.prm.P1c+self.prm.V_sun*1000, 'r')
    #Plotting normalvector of mirror 1
    self.plot_line(self.prm.P1c, self.prm.P1c+self.prm.n_p1*1000, 'c')
    #Plotting lines from M1 to M2 and to the house
    self.plot_line(self.prm.P1c,self.prm.P2c,'r')
    self.plot_line(self.prm.PV,self.prm.P2c, 'r')
    self.set_axes_equal(self.axes)

    #Updating 2D plot
    self.m1 = self.prm.project_m1(self.home_pos)
    x_coords = -(self.m1[:, 0])
    y_coords = self.m1[:, 2]
    self.data_line_m1_circle.setData(x_coords, y_coords)