#!/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()