Skip to content

Instantly share code, notes, and snippets.

@raspberrypisig
Last active December 25, 2025 23:37
Show Gist options
  • Select an option

  • Save raspberrypisig/f18f655bd001a3c9546dc84b9334d213 to your computer and use it in GitHub Desktop.

Select an option

Save raspberrypisig/f18f655bd001a3c9546dc84b9334d213 to your computer and use it in GitHub Desktop.

Top 10 Keyboard Shortcuts (by Category)

f3 global search

f9 adjust last operation used when add is used eg.cube cylinder

🧩 General

G / R / S – Move / Rotate / Scale

Shift + A – Add object

Tab – Object ↔ Edit Mode

Ctrl + A – Apply transforms

N – Sidebar (item, view, tool info)

🧱 Modelling

E – Extrude

I – Inset face

Ctrl + R – Loop cut

Alt + Click – Select edge loop

Ctrl + B – Bevel

💡 Bevel is Blender’s secret weapon.

🎥 Rendering & Shading

Z → Rendered – Switch to rendered view

Shift + A (Shader Editor) – Add shader node

Ctrl + Shift + Click – Preview node (Node Wrangler)

H / Alt + H – Hide / unhide objects

F12 – Render image

🎞 Animation

I – Insert keyframe

Spacebar – Play / pause timeline

Shift + → / ← – Jump keyframes

Alt + G / R / S – Clear transforms

Dope Sheet / Graph Editor – Timing control (essential)

🏁 Minimal Starter Set (If you learn only 5) If you want the highest return shortcuts:

G / R / S

E

Ctrl + B

I

F12

import bpy
import math
# =========================
# USER SETTINGS
# =========================
OBJ_PATH = "/absolute/path/to/toolpath.obj"
NOZZLE_DIAMETER_MM = 0.4
MM_TO_M = 0.001
FPS = 30
DURATION_SECONDS = 8
FRAMES = FPS * DURATION_SECONDS
OUTPUT_PATH = "//print_simulation.webm"
# =========================
# CLEAN SCENE
# =========================
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
scene = bpy.context.scene
scene.unit_settings.system = 'METRIC'
scene.unit_settings.scale_length = 1.0
# =========================
# IMPORT OBJ
# =========================
bpy.ops.import_scene.obj(filepath=OBJ_PATH)
imported = bpy.context.selected_objects
for obj in imported:
obj.scale = (MM_TO_M, MM_TO_M, MM_TO_M)
bpy.ops.object.transform_apply(scale=True)
# =========================
# CONVERT TO CURVES
# =========================
for obj in imported:
bpy.context.view_layer.objects.active = obj
bpy.ops.object.convert(target='CURVE')
# =========================
# NOZZLE PROFILE
# =========================
bpy.ops.curve.primitive_bezier_circle_add(
radius=(NOZZLE_DIAMETER_MM * MM_TO_M) / 2
)
nozzle = bpy.context.active_object
nozzle.name = "NozzleProfile"
# =========================
# BEVEL CURVES → FILAMENT
# =========================
for obj in bpy.context.scene.objects:
if obj.type == 'CURVE' and obj != nozzle:
obj.data.bevel_mode = 'OBJECT'
obj.data.bevel_object = nozzle
obj.data.fill_mode = 'FULL'
# =========================
# CONVERT TO SINGLE MESH
# =========================
filament_objs = [o for o in bpy.context.scene.objects if o.type == 'CURVE' and o != nozzle]
for obj in filament_objs:
bpy.context.view_layer.objects.active = obj
bpy.ops.object.convert(target='MESH')
bpy.ops.object.select_all(action='DESELECT')
for obj in bpy.context.scene.objects:
if obj.type == 'MESH':
obj.select_set(True)
bpy.context.view_layer.objects.active = bpy.context.selected_objects[0]
bpy.ops.object.join()
print_obj = bpy.context.active_object
print_obj.name = "Print"
# =========================
# FILAMENT MATERIAL
# =========================
mat = bpy.data.materials.new("Filament")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
bsdf.inputs["Base Color"].default_value = (0.9, 0.3, 0.1, 1)
bsdf.inputs["Roughness"].default_value = 0.55
bsdf.inputs["Specular"].default_value = 0.3
print_obj.data.materials.append(mat)
# =========================
# CREATE CUTTER (NEGATIVE PRISM)
# =========================
bpy.ops.mesh.primitive_cube_add(size=1)
cutter = bpy.context.active_object
cutter.name = "Cutter"
# Fit cutter to print bounds
bbox = [print_obj.matrix_world @ v.co for v in print_obj.data.vertices]
xs = [v.x for v in bbox]
ys = [v.y for v in bbox]
zs = [v.z for v in bbox]
cutter.scale.x = (max(xs) - min(xs)) * 0.6
cutter.scale.y = (max(ys) - min(ys)) * 0.6
cutter.scale.z = (max(zs) - min(zs)) * 0.6
min_z = min(zs) - cutter.scale.z
max_z = max(zs) + 0.001
cutter.location = (0, 0, min_z)
# =========================
# BOOLEAN MODIFIER
# =========================
mod = print_obj.modifiers.new(name="Reveal", type='BOOLEAN')
mod.operation = 'DIFFERENCE'
mod.object = cutter
mod.solver = 'FAST'
# =========================
# ANIMATE CUTTER (LINEAR!)
# =========================
scene.frame_start = 1
scene.frame_end = FRAMES
cutter.location.z = min_z
cutter.keyframe_insert("location", frame=1)
cutter.location.z = max_z
cutter.keyframe_insert("location", frame=FRAMES)
action = cutter.animation_data.action
for fcurve in action.fcurves:
for kp in fcurve.keyframe_points:
kp.interpolation = 'LINEAR'
# =========================
# CAMERA
# =========================
bpy.ops.object.camera_add(location=(0.4, -0.4, 0.35))
cam = bpy.context.active_object
cam.rotation_euler = (1.1, 0, 0.8)
cam.data.lens = 50
scene.camera = cam
# =========================
# LIGHTING
# =========================
bpy.ops.object.light_add(type='AREA', location=(0.3, -0.3, 0.4))
key = bpy.context.active_object
key.data.energy = 1200
key.data.size = 0.25
bpy.ops.object.light_add(type='AREA', location=(-0.3, -0.2, 0.3))
fill = bpy.context.active_object
fill.data.energy = 500
fill.data.size = 0.35
fill.data.use_shadow = False
bpy.ops.object.light_add(type='AREA', location=(0.0, 0.4, 0.35))
rim = bpy.context.active_object
rim.data.energy = 800
rim.data.size = 0.15
# =========================
# BACKGROUND
# =========================
world = scene.world
world.use_nodes = True
bg = world.node_tree.nodes["Background"]
bg.inputs[0].default_value = (0.15, 0.15, 0.15, 1)
bg.inputs[1].default_value = 1.0
# =========================
# RENDER SETTINGS (WEBM)
# =========================
scene.render.engine = 'BLENDER_EEVEE'
scene.render.fps = FPS
scene.render.use_motion_blur = False
scene.render.image_settings.file_format = 'FFMPEG'
scene.render.ffmpeg.format = 'WEBM'
scene.render.ffmpeg.codec = 'VP9'
scene.render.ffmpeg.constant_rate_factor = 'MEDIUM'
scene.render.filepath = OUTPUT_PATH
# =========================
# RENDER
# =========================
bpy.ops.render.render(animation=True)
import bpy
class SelectOBJ(bpy.types.Operator):
bl_idname = "wm.select_obj"
bl_label = "Select OBJ Toolpath"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
print("Selected file:", self.filepath)
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
bpy.utils.register_class(SelectOBJ)
bpy.ops.wm.select_obj('INVOKE_DEFAULT')
filter_glob: bpy.props.StringProperty(
default="*.obj",
options={'HIDDEN'}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment