├── README.md ├── .gitignore └── split-file.py /README.md: -------------------------------------------------------------------------------- 1 | # STL Splitter: Divide STL Files into Equal Segments for Printing 2 | 3 | This tool divides an STL file into smaller, manageable pieces along the X and Y axes, facilitating easier 3D printing. It's designed to be more efficient and user-friendly than splitting models within slicer software. 4 | 5 | ## Prerequisites 6 | Ensure that you have Python 3 installed on your system. 7 | ```bash 8 | pip3 install trimesh numpy 9 | pip3 install manifold3d 10 | ``` 11 | 12 | ## Usage 13 | ```bash 14 | python split-file.py inputFileName.stl [options] 15 | Options 16 | --xsplit N: Specify the number of divisions along the X-axis. 17 | --ysplit N: Specify the number of divisions along the Y-axis. 18 | --max-x N or -mx N: Set the maximum printable dimension along the X-axis in millimeters. 19 | --max-y N or -my N: Set the maximum printable dimension along the Y-axis in millimeters. 20 | --flip: Flip the STL model over the X-axis before splitting. 21 | ``` 22 | 23 | ## Examples 24 | ### Basic Usage: 25 | ```bash 26 | python split-file.py testfile.stl 27 | This command splits testfile.stl into equal-sized pieces using the default settings. 28 | ``` 29 | 30 | ### Specify Number of Splits: 31 | ```bash 32 | python split-file.py testfile.stl --xsplit 4 --ysplit 6 33 | This command divides testfile.stl into 4 segments along the X-axis and 6 segments along the Y-axis. 34 | ``` 35 | 36 | ### Set Maximum Printable Dimensions: 37 | ```bash 38 | python split-file.py testfile.stl --max-x 200 --max-y 200 39 | This command calculates the necessary divisions to ensure each piece fits within a 200mm x 200mm print area. 40 | ``` 41 | 42 | ### Combine Options: 43 | ``` 44 | python split-file.py testfile.stl --xsplit 3 --max-y 150 --flip 45 | ``` 46 | This command splits testfile.stl into 3 parts along the X-axis, calculates the required divisions along the Y-axis to fit within 150mm, and flips the model over the X-axis before splitting. 47 | 48 | ### Notes 49 | If both --xsplit and --max-x (or --ysplit and --max-y) are provided, the script will prioritize the explicit split count (--xsplit or --ysplit). 50 | The --flip option is useful for adjusting the model's orientation before splitting, depending on your printing requirements. 51 | 52 | Ensure your input STL file is properly formatted and free of errors to avoid issues during the splitting process. 53 | 54 | ### Troubleshooting 55 | Python Version: If you encounter issues running the script, verify that Python 3 is installed on your system. You can check your Python version with: 56 | 57 | ```python3 --version``` 58 | If Python 3 is not installed, you can install it using your package manager. For example, on Debian-based systems: 59 | 60 | ``` 61 | sudo apt-get install python3 62 | After ensuring Python 3 is installed, use python3 to run the script: 63 | ``` 64 | 65 | ```bash 66 | python3 split-file.py inputFileName.stl [options] 67 | ``` 68 | Dependencies: Ensure all required Python packages are installed. You can install them using pip: 69 | 70 | ``` 71 | pip3 install trimesh numpy 72 | ``` 73 | 74 | ### Contributing 75 | If you'd like to contribute to this project, please fork the repository and submit a pull request with your proposed changes. 76 | 77 | ### License 78 | This project is licensed under the MIT License. See the LICENSE file for details. 79 | 80 | vbnet 81 | Copy code 82 | 83 | You can save this content into a file named `README.md` in your project's root directory. This will provide users with clear instructions on how to use your STL splitting tool, including the new features and options you've implemented. 84 | ::contentReference[oaicite:0]{index=0} 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # PyPI configuration file 171 | .pypirc 172 | -------------------------------------------------------------------------------- /split-file.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import trimesh 3 | import argparse 4 | import os 5 | 6 | def calculate_splits(model_size, max_size): 7 | """ 8 | Calculate the number of splits required to fit the model within the maximum printer dimensions. 9 | 10 | Parameters: 11 | model_size (float): Size of the model along an axis. 12 | max_size (float): Maximum printable size along the same axis. 13 | 14 | Returns: 15 | int: Number of splits required. 16 | """ 17 | if max_size is None or max_size <= 0: 18 | raise ValueError("Maximum size must be a positive number.") 19 | splits = np.ceil(model_size / max_size) 20 | return int(splits) 21 | 22 | def split_stl_into_grid(input_stl, xsplit=None, ysplit=None, max_x=None, max_y=None, flip=False): 23 | """ 24 | Splits an STL file into a grid of smaller STL files and prints their dimensions. 25 | 26 | Parameters: 27 | input_stl (str): Path to the input STL file. 28 | xsplit (int): Number of divisions along the X-axis. 29 | ysplit (int): Number of divisions along the Y-axis. 30 | max_x (float): Maximum printable dimension along the X-axis in mm. 31 | max_y (float): Maximum printable dimension along the Y-axis in mm. 32 | flip (bool): Whether to flip the STL model over the X-axis. 33 | """ 34 | # Load the STL file 35 | mesh = trimesh.load(input_stl) 36 | if not isinstance(mesh, trimesh.Trimesh): 37 | raise ValueError("Failed to load STL as a valid 3D mesh") 38 | 39 | # Flip the mesh if specified 40 | if flip: 41 | # Apply a 180-degree rotation around the X-axis 42 | rotation_matrix = trimesh.transformations.rotation_matrix(np.pi, [1, 0, 0]) 43 | mesh.apply_transform(rotation_matrix) 44 | 45 | # Get the bounding box dimensions 46 | bounds = mesh.bounds 47 | model_size_x = bounds[1][0] - bounds[0][0] 48 | model_size_y = bounds[1][1] - bounds[0][1] 49 | model_size_z = bounds[1][2] - bounds[0][2] 50 | 51 | # Print main part dimensions 52 | print(f"Main part dimensions (mm): X: {model_size_x:.2f}, Y: {model_size_y:.2f}, Z: {model_size_z:.2f}") 53 | 54 | # Determine the number of splits 55 | if xsplit is None: 56 | if max_x is not None: 57 | xsplit = calculate_splits(model_size_x, max_x) 58 | else: 59 | xsplit = 1 # Default to 1 if neither xsplit nor max_x is provided 60 | 61 | if ysplit is None: 62 | if max_y is not None: 63 | ysplit = calculate_splits(model_size_y, max_y) 64 | else: 65 | ysplit = 1 # Default to 1 if neither ysplit nor max_y is provided 66 | 67 | # Ensure at least one split 68 | xsplit = max(1, xsplit) 69 | ysplit = max(1, ysplit) 70 | 71 | # Calculate subdivision sizes 72 | segment_size_x = model_size_x / xsplit 73 | segment_size_y = model_size_y / ysplit 74 | 75 | # Print subdivision sizes 76 | print(f"Number of divisions: X: {xsplit}, Y: {ysplit}") 77 | print(f"Each segment size (mm): X: {segment_size_x:.2f}, Y: {segment_size_y:.2f}, Z: {model_size_z:.2f}") 78 | 79 | # Determine the output prefix 80 | output_prefix = os.path.splitext(input_stl)[0] # Strip .stl extension 81 | 82 | # Calculate extents 83 | x_extent = np.linspace(bounds[0][0], bounds[1][0], xsplit + 1) 84 | y_extent = np.linspace(bounds[0][1], bounds[1][1], ysplit + 1) 85 | z_min, z_max = bounds[0][2], bounds[1][2] 86 | 87 | # Split the mesh into grid cells 88 | part_number = 1 89 | for i in range(xsplit): 90 | for j in range(ysplit): 91 | # Define the bounding box for the current grid cell 92 | x_min, x_max = x_extent[i], x_extent[i + 1] 93 | y_min, y_max = y_extent[j], y_extent[j + 1] 94 | bounds_box = trimesh.creation.box( 95 | extents=(x_max - x_min, y_max - y_min, z_max - z_min), 96 | transform=trimesh.transformations.translation_matrix( 97 | [(x_max + x_min) / 2, (y_max + y_min) / 2, (z_max + z_min) / 2] 98 | ) 99 | ) 100 | 101 | # Intersect the mesh with the bounding box 102 | section = mesh.intersection(bounds_box) 103 | 104 | # Save the section if it contains geometry 105 | if section.is_empty: 106 | continue 107 | 108 | # Save the section to an STL file 109 | output_filename = f"{output_prefix}_splt-{part_number:02d}.stl" 110 | section.export(output_filename) 111 | print(f"Saved: {output_filename}") 112 | part_number += 1 113 | 114 | if __name__ == "__main__": 115 | # Parse command-line arguments 116 | parser = argparse.ArgumentParser(description="Split an STL file into a grid of smaller STL files.") 117 | parser.add_argument("input_stl", help="Path to the input STL file") 118 | parser.add_argument("--xsplit", type=int, default=None, help="Number of divisions along the X-axis") 119 | parser.add_argument("--ysplit", type=int, default=None, help="Number of divisions along the Y-axis") 120 | parser.add_argument("--max-x", "-mx", type=float, default=None, help="Maximum printable dimension along the X-axis in mm") 121 | parser.add_argument("--max-y", "-my", type=float, default=None, help="Maximum printable dimension along the Y-axis in mm") 122 | parser.add_argument("--flip", action="store_true", help="Flip the STL model over the X-axis before splitting") 123 | 124 | args = parser.parse_args() 125 | 126 | # Run the splitting function 127 | split_stl_into_grid( 128 | input_stl=args.input_stl, 129 | xsplit=args.xsplit, 130 | ysplit=args.ysplit, 131 | max_x=args.max_x, 132 | max_y=args.max_y, 133 | flip=args.flip 134 | ) 135 | --------------------------------------------------------------------------------