Plot3D Data Format
====================

Data format for plot3D comes from this 1990s manual  :download:`pdf <../_static/plot3d_manual.pdf>` Plot3D is a simple way to construct a structured grid using 4 points to define a cell in 2D and 8 points for 3D. 

Plot3D files are organized as follows 

.. code-block:: text
   :linenos:

    8                                   # Number of blocks 
       5       5       5                # Block 1 is shaped 5x5x5 in i,j,k this is 125 nodes 
       5       5       5                # Block 2 shape 
       5       5       5
       5       5       5
       5       5       5
       5       5       5
       5       5       5
       5       5       5
    1.08569236018e-07  2.89960779720e-07  2.99150448968e-07  2.55513369907e-07          # First 125 values describe are X, next 25 are Y, last 25 are Z 
    2.12244548714e-07  1.01483086157e-07  2.67218012828e-07  2.71062573276e-07 
    2.24420893535e-07  1.78210401103e-07  7.47886754748e-08  1.93926065872e-07 
    1.84719158858e-07  1.38925249638e-07  1.00523800506e-07  1.71518139691e-08 
    9.37154616132e-08  9.36074304736e-08  5.80944219397e-08  2.00380892990e-08 
    -5.46866818496e-08 -1.24404013757e-08  1.15859071226e-08 -4.76059469623e-09 
   

Knowing this, reading the files is fairly simple to do in python. The following code blocks demonstrate how to read a simple ASCII or binary file. Calling the function ``read_plot3D(filename:str, binary:bool=True,big_endian:bool=False)``

.. code-block:: python
    :linenos:

    import numpy as np 
    import os.path as osp
    import struct
    from typing import List
    from .block import Block

    def __read_plot3D_chunk_binary(f,IMAX:int,JMAX:int,KMAX:int, big_endian:bool=False):
        """Reads and formats a binary chunk of data into a plot3D block

        Args:
            f (io): file handle
            IMAX (int): maximum I index
            JMAX (int): maximum J index
            KMAX (int): maximum K index
            big_endian (bool, Optional): Use big endian format for reading binary files. Defaults False.

        Returns:
            numpy.ndarray: Plot3D variable either X,Y, or Z 
        """
        A = np.empty(shape=(IMAX, JMAX, KMAX))
        for k in range(KMAX):
            for j in range(JMAX):
                for i in range(IMAX):
                    A[i,j,k] = struct.unpack(">f",f.read(4))[0] if big_endian else struct.unpack("f",f.read(4))[0]
        return A

    def __read_plot3D_chunk_ASCII(tokenArray:List[str],offset:int,IMAX:int,JMAX:int,KMAX:int):
        """Reads an ascii chunk of plot3D data into a block 

        Args:
            tokenArray (List[str]): this is a list of strings separated by a space, new line character removed ["12","22", ... etc]
            offset (int): how many entries to skip in the array based on block size (IMAX*JMAX*KMAX) of the previous block
            IMAX (int): maximum I index
            JMAX (int): maximum J index
            KMAX (int): maximum K index 

        Returns:
            numpy.ndarray: Plot3D variable either X,Y, or Z
        """
        '''Works for ASCII files 
        '''
        A = np.empty(shape=(IMAX, JMAX, KMAX))
        for k in range(KMAX):
            for j in range(JMAX):
                for i in range(IMAX):
                    A[i,j,k] = tokenArray[offset]
                    offset+=1

        return A, offset

    def read_plot3D(filename:str, binary:bool=True,big_endian:bool=False):
        """Reads a plot3d file and returns Blocks

        Args:
            filename (str): name of the file to read, .p3d, .xyz, .pdc, .plot3d? 
            binary (bool, optional): indicates if the file is binary. Defaults to True.
            big_endian (bool, optional): use big endian format for reading binary files

        Returns:
            List[Block]: List of blocks insdie the plot3d file
        """
        
        blocks = list()
        if osp.isfile(filename):
            if binary:
                with open(filename,'rb') as f:
                    nblocks = struct.unpack(">I",f.read(4))[0] if big_endian else struct.unpack("I",f.read(4))[0] # Read bytes            
                    IMAX = list(); JMAX = list(); KMAX = list()
                    for b in range(nblocks):
                        if big_endian:
                            IMAX.append(struct.unpack(">I",f.read(4))[0]) # Read bytes
                            JMAX.append(struct.unpack(">I",f.read(4))[0]) # Read bytes
                            KMAX.append(struct.unpack(">I",f.read(4))[0]) # Read bytes
                        else:
                            IMAX.append(struct.unpack("I",f.read(4))[0]) # Read bytes
                            JMAX.append(struct.unpack("I",f.read(4))[0]) # Read bytes
                            KMAX.append(struct.unpack("I",f.read(4))[0]) # Read bytes

                    for b in range(nblocks):
                        X = __read_plot3D_chunk_binary(f,IMAX[b],JMAX[b],KMAX[b], big_endian)
                        Y = __read_plot3D_chunk_binary(f,IMAX[b],JMAX[b],KMAX[b], big_endian)
                        Z = __read_plot3D_chunk_binary(f,IMAX[b],JMAX[b],KMAX[b], big_endian)
                        b_temp = Block(X,Y,Z)                    
                        blocks.append(b_temp)
            else:
                with open(filename,'r') as f: 
                    nblocks = int(f.readline())
                    IMAX = list(); JMAX = list(); KMAX = list()
                    
                    for b in range(nblocks):
                        tokens = [int(w.replace('\n','')) for w in f.readline().split(' ') if w]
                        IMAX.append(tokens[0])
                        JMAX.append(tokens[1])
                        KMAX.append(tokens[2])
                    
                    lines = [l.replace('\n','').split(' ') for l in f.readlines()] # Basically an array of strings representing numbers
                    lines = [item for sublist in lines for item in sublist]         # Flatten list of lists https://stackabuse.com/python-how-to-flatten-list-of-lists/
    
                    tokenArray = [float(entry) for entry in lines if entry] # Convert everything to float 
                    offset = 0
                    for b in range(nblocks):
                        X, offset = __read_plot3D_chunk_ASCII(tokenArray,offset,IMAX[b],JMAX[b],KMAX[b])
                        Y, offset = __read_plot3D_chunk_ASCII(tokenArray,offset,IMAX[b],JMAX[b],KMAX[b])
                        Z, offset = __read_plot3D_chunk_ASCII(tokenArray,offset,IMAX[b],JMAX[b],KMAX[b])
                        b_temp = Block(X,Y,Z)                    
                        blocks.append(b_temp)
        return blocks