Skip to content

Instantly share code, notes, and snippets.

@ConnerWill
Last active April 29, 2025 16:35
Show Gist options
  • Save ConnerWill/89efc61e26c1418803bcb4ba1d7f8413 to your computer and use it in GitHub Desktop.
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.
"""
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