Last active
April 29, 2025 16:35
-
-
Save ConnerWill/89efc61e26c1418803bcb4ba1d7f8413 to your computer and use it in GitHub Desktop.
Engraves an SVG pattern onto the surface of an STL model using Blender’s Python API with shrinkwrap, solidify, and boolean modifiers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Usage: | |
blender --background --python wrap_pattern.py -- \ | |
--stl flask.stl \ | |
--svg pattern.svg \ | |
--output flask_wrapped.stl | |
Notes: | |
- The -- is required before the Python script arguments. | |
- All paths are relative to this script's directory or absolute. | |
- Output will be written to --output (default: flask_wrapped.stl). | |
Requires: | |
- Blender 2.80+ | |
- Built-in Python modules only (no pip install required) | |
""" | |
import bpy | |
import os | |
import sys | |
import argparse | |
# --- PARSE ARGS --- | |
def parse_args(): | |
argv = sys.argv | |
if "--" in argv: | |
argv = argv[argv.index("--") + 1:] | |
else: | |
argv = [] | |
parser = argparse.ArgumentParser(description="Engrave SVG pattern onto STL surface using Blender") | |
parser.add_argument("--stl", required=True, help="Path to STL model") | |
parser.add_argument("--svg", required=True, help="Path to SVG pattern") | |
parser.add_argument("--output", default="flask_wrapped.stl", help="Output STL filename") | |
parser.add_argument("--verbose", action="store_true", help="Enable verbose output") | |
return parser.parse_args() | |
# --- MAIN --- | |
def main(): | |
args = parse_args() | |
# --- RESOLVE PATHS --- | |
script_dir = os.path.dirname(os.path.abspath(__file__)) | |
stl_path = os.path.abspath(os.path.join(script_dir, args.stl)) | |
svg_path = os.path.abspath(os.path.join(script_dir, args.svg)) | |
output_path = os.path.abspath(os.path.join(script_dir, args.output)) | |
# --- LOGGING --- | |
if args.verbose: | |
print(f"STL Path: {stl_path}") | |
print(f"SVG Path: {svg_path}") | |
print(f"Output Path: {output_path}") | |
# --- CLEAR SCENE --- | |
bpy.ops.wm.read_factory_settings(use_empty=True) | |
# --- IMPORT STL --- | |
bpy.ops.import_mesh.stl(filepath=stl_path) | |
stl_obj = bpy.context.selected_objects[0] | |
stl_obj.name = "BaseModel" | |
# --- IMPORT SVG --- | |
bpy.ops.import_curve.svg(filepath=svg_path) | |
svg_objs = [obj for obj in bpy.context.selected_objects if obj.type == 'CURVE'] | |
# Convert SVG curves to mesh | |
mesh_objs = [] | |
for curve_obj in svg_objs: | |
bpy.context.view_layer.objects.active = curve_obj | |
curve_obj.select_set(True) | |
bpy.ops.object.convert(target='MESH') | |
mesh_objs.append(curve_obj) | |
# Join all SVG parts into one object | |
for obj in bpy.context.selected_objects: | |
obj.select_set(False) | |
for obj in mesh_objs: | |
obj.select_set(True) | |
bpy.context.view_layer.objects.active = mesh_objs[0] | |
bpy.ops.object.join() | |
pattern_obj = bpy.context.active_object | |
pattern_obj.name = "Pattern" | |
# --- POSITION & WRAP --- | |
pattern_obj.location.z += 0.01 # ensure slight contact | |
shrinkwrap = pattern_obj.modifiers.new(name="Shrinkwrap", type='SHRINKWRAP') | |
shrinkwrap.target = stl_obj | |
shrinkwrap.wrap_method = 'PROJECT' | |
shrinkwrap.wrap_mode = 'ON_SURFACE' | |
shrinkwrap.use_negative_direction = True | |
shrinkwrap.use_positive_direction = True | |
shrinkwrap.offset = 0.001 | |
bpy.ops.object.modifier_apply(modifier="Shrinkwrap") | |
# --- SOLIDIFY FOR DEPTH --- | |
solidify = pattern_obj.modifiers.new(name="Solidify", type='SOLIDIFY') | |
solidify.thickness = 0.5 | |
bpy.ops.object.modifier_apply(modifier="Solidify") | |
# --- BOOLEAN DIFFERENCE --- | |
bool_mod = stl_obj.modifiers.new(name="Boolean", type='BOOLEAN') | |
bool_mod.operation = 'DIFFERENCE' | |
bool_mod.object = pattern_obj | |
bpy.context.view_layer.objects.active = stl_obj | |
bpy.ops.object.modifier_apply(modifier="Boolean") | |
# Optional cleanup | |
bpy.data.objects.remove(pattern_obj, do_unlink=True) | |
# --- EXPORT FINAL STL --- | |
bpy.ops.export_mesh.stl(filepath=output_path) | |
if args.verbose: | |
print(f"Engraved STL saved to {output_path}") | |
# --- ENTRY POINT --- | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment