solution_256.py

#!/usr/bin/python3
# ===============================================================
# Dsplay STL ("StereoLithography") file internals
#
# Note: the Python struct modules 'f' and 'd' conversion codes
#       uses the packed representation IEEE 754, binary32
#       (for 'f') or binary64 (for 'd') format, regardless
#       of the floating-point format used by the platform
# ===============================================================
#
# 1. Interpret strings as packed binary data
#    pydoc-zh.readthedocs.io/en/latest/library/struct.html
#
# 2. struct documentation
#    docs.python.org/3/library/struct.htm
#
# 3. STL (file format)
#    en.wikipedia.org/wiki/STL_(file_format)
#
# 4. STLB Files - Stereolithography (binary) 
#    people.sc.fsu.edu/~jburkardt/data/stlb/stlb.html
#
# 5. FileFormat
#    docs.fileformat.com/cad/stl/
#
# 6. The StL Binary Format
#    fabbers.com/tech/STL_Format#Sct_binary
#
# 7. What's inside an .STL?
#    Edit an .STL file using a text editor!
#    www.youtube.com/watch?v=7FmW6qhaupw
# 
# ===============================================================

import sys
import struct
from hex_dump import hex_dump
import user_interface as ui

program_description ='''\
+ ----------------------------------------------------+
| test and display binary float data using a STL file |
+ ----------------------------------------------------+'''

# ---------------------------------------------------------------
# ---- display vector (3 x 32bit floats)
#
# ---- Notes:
# ----   1. struct.unpack returns a tuple (get first element)
# ----   2. 'f' is for 32bit 4byte float numbers
# ----   3. 'd' is for 64bit 8byte float numbers
# ---------------------------------------------------------------

def display_xyz(xyz_byts:bytes,title='xyz') -> None:

    if len(xyz_byts) != 12:
        print()
        print(f'bad length of xyz bytes ({len(byts)})')
        print()
        sys.exit()

    [x] = struct.unpack('f', xyz_byts[0:4])
    [y] = struct.unpack('f', xyz_byts[4:8])
    [z] = struct.unpack('f', xyz_byts[8:12])

    print(f'{title:<8} = {x:>6.02f} {y:>6.02f} {z:>6.02f}')

# ---------------------------------------------------------------
# ---- display facet
# ---------------------------------------------------------------

def display_facet(fac_byts:bytearray) -> None:

    idx  = 0                         # byte array index

    display_xyz(fac_byts[idx:idx+12],'norm')
    idx += 12
    display_xyz(fac_byts[idx:idx+12],'v1')
    idx += 12
    display_xyz(fac_byts[idx:idx+12],'v2')
    idx += 12
    display_xyz(fac_byts[idx:idx+12],'v3')

# ---------------------------------------------------------------
# ---- display STL file
# ----
# ---- Note: the STL binary file format uses the IEEE integer
# ----       and floating point numerical representation 
# ---------------------------------------------------------------

def display_stl_file(file_path, stl_bytes:bytearray) -> None:

    title = stl_bytes[0:80].strip().decode('UTF-8')
    
    facets = int.from_bytes(stl_bytes[80:84],'little')

    print(f'file     = {file_path}')
    print(f'title    = "{title}"')
    print(f'facets   = {facets}')

    ##loop         = 0                 # use for debugging
    ##loop_count   = 2                 # use for debugging

    facet_count  = 0                 # facet counter 
    facet_offset = 84                # start of facets
                                     # (title + facet count)
    
    for facit in range(facets):

        ##loop +=1                     # use for debugging
        ##if loop > loop_count: break  # use for debugging

        facet_count += 1

        print(f'------------ facet {facet_count} ------------')

        display_facet(stl_bytes[facet_offset:facet_offset+50])

        facet_offset += 50

    return facet_count

# ---------------------------------------------------------------
# ---- open STL (binary) and read it directly into a byte array
# ---------------------------------------------------------------
    
def load_stl_to_array(stl_file_path:str) -> bytearray:

    with open(stl_file_path,'rb') as file:
        stl_bytes = file.read()
    return stl_bytes

# ---------------------------------------------------------------
# ---- test if the system is 'big' endia or 'little' endia
# ---------------------------------------------------------------
import sys

def test_for_little_endia():
    if sys.byteorder=='big':
        return False
    return True

# ---------------------------------------------------------------
# ---- create a string from a byte array
# ---------------------------------------------------------------

def my_byte_string(byts:bytes) -> None:
    lst = []
    for byt in byts: lst.append('\\x' + f'{byt:02X} ')
    return ''.join(lst)

# ---------------------------------------------------------------
# ---- test/display struct float packing and unpacking
# ---------------------------------------------------------------

def test_struct_float_pack_unpack():

    while True:

        print()
        s = ui.get_user_input('Enter a float: ')
        if not s: break
        tf,flt = ui.is_float(s)
        if tf is not True:
            print('OOPS! Try again!')
            continue

        print()
        pflt = struct.pack('f', flt)         # pack float
        print(f'flt  type = {type(flt)}')
        print(f'pflt type = {type(pflt)}')
        print(f'pflt len  = {len(pflt)}')
        ##print(f'pflt      = {pflt}')
        print(f'pflt      = {my_byte_string(pflt)}')
        ##hex_dump(pflt,0,len(pflt)-1)
        
        print()

        [xflt] = struct.unpack('f', pflt)    # unpack float
        print(f'xflt type = {type(xflt)}')
        print(f'xflt      = {xflt}')
        sflt = str(xflt)
        print(f'sflt type = {type(sflt)}')
        print(f'sflt      = {sflt}')

# ---------------------------------------------------------------
# ---- main
# ---------------------------------------------------------------

if __name__ == '__main__':

    # ---- system's endia?

    print()
    if test_for_little_endia():
        print(f'system is \'little\' endia')
    else:
        print(f'system is \'big\' endia')
        print()
        print('this code is for \'little\' endia')
        print('you must modify code for \'big\' endia')
        print()
        sys.exit()

    # ---- system's float max, min, epsilon

    print()
    print("Max float value:", sys.float_info.max)
    print("Min float value:", sys.float_info.min)
    print("Machine epsilon:", sys.float_info.epsilon)

    ## ---- test packing floats
    ##
    ##test_struct_float_pack_unpack()
    ##sys.exit()

    # ---- display program description

    print()
    print(program_description)

    # ---- process STL ("StereoLithography")  file
    
    file_path = 'stl_binary_cube.stl'

    # load file bytes into memory

    stl_bytes = load_stl_to_array(file_path)

    # ---- display STL file

    print()
    facet_count = display_stl_file(file_path,stl_bytes)

    print()

    print(f'{facet_count} facets displayed')
        
    print()