{{tag>projects cloud club computing virtualization machines VMs AWS Azure GCP}} [[python_club|About the Club]]\\ ==== Python Club Topics - Exercise: Reduce 3D stl file size ==== ==== Exercise: Reduce the file size of a given 3D STL file ( .stl ) ==== - Output: Accept arguments of input + output files and a reduction factor ( % as number between 0 and .99 ) or as percent with --percent option. - What you learn from the example: - File manipulation - STL model manipulation - Use an external application/tool/command ( MeshLab in this case ) to complete a task - Use specific python libraries ==== Solution ==== #!/usr/bin/env python3 ''' STL Reducer - A utility to reduce the file size of 3D STL files. This script takes an STL file and reduces its size by simplifying the mesh while attempting to preserve the overall shape and features of the 3D model. Requirements: - numpy-stl - pymeshlab Usage: python stl_reducer.py --input INPUT_FILE --output OUTPUT_FILE --reduction REDUCTION_FACTOR Example: python stl_reducer.py --input model.stl --output model_reduced.stl --reduction 0.5 python stl_reducer.py --input model.stl --output model_reduced.stl --reduction 50 --percent ''' import os import sys import time import argparse import numpy as np from stl import mesh import pymeshlab def check_file_size(file_path): ''' Get file size in bytes and human-readable format. Args: file_path: Path to the file Returns: tuple: (size_in_bytes, human_readable_size) ''' size_bytes = os.path.getsize(file_path) # Convert to human-readable format units = ['B', 'KB', 'MB', 'GB'] size = size_bytes unit_index = 0 while size >= 1024 and unit_index < len(units) - 1: size /= 1024 unit_index += 1 return size_bytes, f'{size:.2f} {units[unit_index]}' def validate_stl(file_path): ''' Validate that the file is a proper STL file. Args: file_path: Path to the STL file Returns: bool: True if valid, False otherwise ''' try: # Try to load the mesh test_mesh = mesh.Mesh.from_file(file_path) # Check if the mesh has valid data if test_mesh.data.size == 0: return False return True except Exception: return False def reduce_stl(input_file, output_file, reduction_factor, is_percent=False): ''' Reduce STL file size by simplifying the mesh. Args: input_file: Path to the input STL file output_file: Path to save the reduced STL file reduction_factor: Factor to reduce by (0-1) or percentage (1-100) is_percent: Whether the reduction factor is a percentage Returns: tuple: (original_size, reduced_size) in bytes ''' # Convert percentage to decimal if needed if is_percent: if reduction_factor < 1 or reduction_factor > 99: raise ValueError('Percentage must be between 1 and 99') reduction_factor = reduction_factor / 100 # Check that reduction factor is valid if reduction_factor <= 0 or reduction_factor >= 1: if not is_percent: raise ValueError('Reduction factor must be between 0 and 1') print(f'Loading STL file: {input_file}') original_size_bytes, original_size_hr = check_file_size(input_file) print(f'Original size: {original_size_hr}') # Create a MeshSet ms = pymeshlab.MeshSet() # Load the mesh ms.load_new_mesh(input_file) # Get initial triangle count initial_triangles = ms.current_mesh().face_number() print(f'Initial triangle count: {initial_triangles}') # Calculate target number of faces (triangles) target_faces = int(initial_triangles * reduction_factor) print(f'Target triangle count: {target_faces}') # Apply quadric edge collapse decimation print('Reducing mesh...') ms.apply_filter('meshing_decimation_quadric_edge_collapse', targetfacenum=target_faces, preserveboundary=True, preservenormal=True, optimalplacement=True) # Save the reduced mesh print(f'Saving reduced mesh to: {output_file}') ms.save_current_mesh(output_file) # Check the reduced file size reduced_size_bytes, reduced_size_hr = check_file_size(output_file) # Calculate reduction percentage reduction_percent = ((original_size_bytes - reduced_size_bytes) / original_size_bytes) * 100 # Get final triangle count final_triangles = ms.current_mesh().face_number() triangle_reduction_percent = ((initial_triangles - final_triangles) / initial_triangles) * 100 return original_size_bytes, reduced_size_bytes, initial_triangles, final_triangles def main(): '''Main function to handle CLI arguments and execute the reduction process.''' parser = argparse.ArgumentParser( description='Reduce the size of STL files by simplifying the mesh.', epilog='Example: python stl_reducer.py --input model.stl --output model_reduced.stl --reduction 0.5' ) parser.add_argument('--input', '-i', required=True, help='Input STL file path') parser.add_argument('--output', '-o', required=True, help='Output STL file path') parser.add_argument('--reduction', '-r', type=float, required=True, help='Reduction factor (0-1) or percentage (1-100 with --percent)') parser.add_argument('--percent', '-p', action='store_true', help='Interpret reduction factor as percentage to keep (1-99)') args = parser.parse_args() # Check if input file exists if not os.path.isfile(args.input): print(f'Error: Input file '{args.input}' not found.') sys.exit(1) # Validate STL file if not validate_stl(args.input): print(f'Error: '{args.input}' is not a valid STL file.') sys.exit(1) # Check if output path is writable output_dir = os.path.dirname(args.output) if output_dir and not os.access(output_dir, os.W_OK): print(f'Error: Cannot write to output directory '{output_dir}'.') sys.exit(1) try: # Start timer start_time = time.time() # Perform the reduction original_size, reduced_size, initial_triangles, final_triangles = reduce_stl( args.input, args.output, args.reduction, args.percent ) # End timer elapsed_time = time.time() - start_time # Calculate reduction percentages size_reduction_percent = ((original_size - reduced_size) / original_size) * 100 triangle_reduction_percent = ((initial_triangles - final_triangles) / initial_triangles) * 100 # Print results print('\nReduction completed successfully!') print(f'Elapsed time: {elapsed_time:.2f} seconds') print('\nResults:') print(f'Original size: {original_size / 1024:.2f} KB') print(f'Reduced size: {reduced_size / 1024:.2f} KB') print(f'Size reduction: {size_reduction_percent:.2f}%') print(f'Original triangle count: {initial_triangles}') print(f'Reduced triangle count: {final_triangles}') print(f'Triangle reduction: {triangle_reduction_percent:.2f}%') except ValueError as ve: print(f'Error: {ve}') sys.exit(1) except Exception as e: print(f'An unexpected error occurred: {e}') sys.exit(1) if __name__ == '__main__': main()