File indexing completed on 2026-11-09 10:47:17
0001
0002
0003 """
0004 scale_stl.py
0005
0006 Scale an STL mesh about its centre and write the result to a new STL.
0007
0008 Usage:
0009 python scale_stl.py input.stl output.stl --scale 1.01
0010
0011 Examples:
0012 --scale 1.01 Increase dimensions by 1%
0013 --scale 0.99 Decrease dimensions by 1%
0014 --scale 2.0 Double the dimensions
0015
0016 The centre of the mesh remains unchanged.
0017 """
0018
0019 import argparse
0020 import sys
0021 from pathlib import Path
0022
0023 import numpy as np
0024 import trimesh
0025
0026
0027 def load_mesh(path):
0028 mesh = trimesh.load_mesh(
0029 path,
0030 force="mesh",
0031 process=True
0032 )
0033
0034 if not isinstance(mesh, trimesh.Trimesh):
0035 raise ValueError(
0036 "{} did not load as a single mesh.".format(path)
0037 )
0038
0039 return mesh
0040
0041
0042 def scale_mesh_about_centre(mesh, scale):
0043 """
0044 Scale mesh about its centroid.
0045
0046 x_new = centre + scale * (x - centre)
0047 """
0048
0049 if scale <= 0:
0050 raise ValueError("Scale factor must be greater than zero.")
0051
0052 scaled = mesh.copy()
0053
0054 centre = np.asarray(mesh.centroid, dtype=float)
0055
0056 scaled.vertices = (
0057 centre
0058 + scale * (mesh.vertices - centre)
0059 )
0060
0061 return scaled, centre
0062
0063
0064 def main():
0065
0066 parser = argparse.ArgumentParser(
0067 description=(
0068 "Scale an STL mesh about its centre while "
0069 "keeping the centre coordinate unchanged."
0070 )
0071 )
0072
0073 parser.add_argument(
0074 "input",
0075 help="Input STL file"
0076 )
0077
0078 parser.add_argument(
0079 "output",
0080 help="Output STL file"
0081 )
0082
0083 parser.add_argument(
0084 "--scale",
0085 type=float,
0086 required=True,
0087 help=(
0088 "Scale factor. For example, 1.01 increases the "
0089 "dimensions by 1 percent."
0090 )
0091 )
0092
0093 args = parser.parse_args()
0094
0095
0096 try:
0097 mesh = load_mesh(args.input)
0098 except Exception as exc:
0099 print(
0100 "Error loading mesh: {}".format(exc),
0101 file=sys.stderr
0102 )
0103 sys.exit(1)
0104
0105 print("Input mesh:")
0106 print(" vertices: {}".format(len(mesh.vertices)))
0107 print(" faces: {}".format(len(mesh.faces)))
0108 print(" watertight: {}".format(mesh.is_watertight))
0109 print(" volume: {}".format(abs(mesh.volume)))
0110
0111
0112 try:
0113 scaled, centre = scale_mesh_about_centre(
0114 mesh,
0115 args.scale
0116 )
0117 except Exception as exc:
0118 print(
0119 "Error scaling mesh: {}".format(exc),
0120 file=sys.stderr
0121 )
0122 sys.exit(1)
0123
0124 print()
0125 print("Scaling:")
0126 print(" scale factor: {}".format(args.scale))
0127 print(
0128 " centre: ({:.6g}, {:.6g}, {:.6g})".format(
0129 centre[0],
0130 centre[1],
0131 centre[2]
0132 )
0133 )
0134
0135 print()
0136 print("Output mesh:")
0137 print(" vertices: {}".format(len(scaled.vertices)))
0138 print(" faces: {}".format(len(scaled.faces)))
0139 print(" watertight: {}".format(scaled.is_watertight))
0140 print(" volume: {}".format(abs(scaled.volume)))
0141
0142
0143 output_path = Path(args.output)
0144
0145 try:
0146 scaled.export(str(output_path))
0147 except Exception as exc:
0148 print(
0149 "Error writing output: {}".format(exc),
0150 file=sys.stderr
0151 )
0152 sys.exit(1)
0153
0154 print()
0155 print("Wrote:")
0156 print(" {}".format(output_path))
0157
0158
0159 if __name__ == "__main__":
0160 main()