128 lines
4.2 KiB
Python
128 lines
4.2 KiB
Python
"""Render six inspection views of an STL using Blender.
|
|
|
|
Usage:
|
|
blender --background --python tools/render_stl_previews.py -- \
|
|
input.stl output-directory basename [comma-separated-views]
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
import bpy
|
|
from mathutils import Vector
|
|
|
|
|
|
def arguments():
|
|
args = sys.argv[sys.argv.index("--") + 1 :]
|
|
if len(args) not in (3, 4):
|
|
raise SystemExit(
|
|
"expected: input.stl output-directory basename [comma-separated-views]"
|
|
)
|
|
requested_views = args[3].split(",") if len(args) == 4 else None
|
|
return args[0], args[1], args[2], requested_views
|
|
|
|
|
|
def look_at(camera, target):
|
|
direction = Vector(target) - camera.location
|
|
camera.rotation_euler = direction.to_track_quat("-Z", "Y").to_euler()
|
|
|
|
|
|
def render_view(camera, center, span, name, direction, output_dir, basename):
|
|
distance = span * 2.2
|
|
camera.location = Vector(center) + Vector(direction).normalized() * distance
|
|
look_at(camera, center)
|
|
camera.data.type = "ORTHO"
|
|
camera.data.ortho_scale = span * 1.35
|
|
bpy.context.scene.render.filepath = os.path.join(
|
|
output_dir, f"{basename}_{name}.png"
|
|
)
|
|
bpy.ops.render.render(write_still=True)
|
|
|
|
|
|
def main():
|
|
input_path, output_dir, basename, requested_views = arguments()
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
bpy.ops.wm.stl_import(filepath=os.path.abspath(input_path))
|
|
model = bpy.context.selected_objects[0]
|
|
model.name = "Feather case"
|
|
|
|
material = bpy.data.materials.new("Case material")
|
|
material.diffuse_color = (0.16, 0.42, 0.72, 1.0)
|
|
material.metallic = 0.0
|
|
material.roughness = 0.45
|
|
model.data.materials.append(material)
|
|
|
|
corners = [model.matrix_world @ Vector(corner) for corner in model.bound_box]
|
|
mins = Vector([min(v[i] for v in corners) for i in range(3)])
|
|
maxs = Vector([max(v[i] for v in corners) for i in range(3)])
|
|
center = (mins + maxs) / 2
|
|
span = max(maxs - mins)
|
|
|
|
world = bpy.data.worlds.new("Inspection world")
|
|
world.use_nodes = True
|
|
world.node_tree.nodes["Background"].inputs["Color"].default_value = (
|
|
0.035,
|
|
0.04,
|
|
0.05,
|
|
1,
|
|
)
|
|
world.node_tree.nodes["Background"].inputs["Strength"].default_value = 0.5
|
|
bpy.context.scene.world = world
|
|
|
|
camera_data = bpy.data.cameras.new("Camera")
|
|
camera = bpy.data.objects.new("Camera", camera_data)
|
|
bpy.context.collection.objects.link(camera)
|
|
bpy.context.scene.camera = camera
|
|
|
|
key_data = bpy.data.lights.new("Key", type="AREA")
|
|
key_data.energy = 900
|
|
key_data.shape = "DISK"
|
|
key_data.size = span
|
|
key = bpy.data.objects.new("Key", key_data)
|
|
key.location = center + Vector((span, -span, span * 1.5))
|
|
bpy.context.collection.objects.link(key)
|
|
|
|
fill_data = bpy.data.lights.new("Fill", type="AREA")
|
|
fill_data.energy = 500
|
|
fill_data.size = span
|
|
fill = bpy.data.objects.new("Fill", fill_data)
|
|
fill.location = center + Vector((-span, span, span))
|
|
bpy.context.collection.objects.link(fill)
|
|
|
|
scene = bpy.context.scene
|
|
scene.render.engine = "BLENDER_WORKBENCH"
|
|
scene.render.resolution_x = 800
|
|
scene.render.resolution_y = 600
|
|
scene.render.resolution_percentage = 100
|
|
scene.render.image_settings.file_format = "PNG"
|
|
scene.render.film_transparent = False
|
|
scene.display.shading.light = "STUDIO"
|
|
scene.display.shading.color_type = "MATERIAL"
|
|
scene.display.shading.show_shadows = True
|
|
scene.display.shading.show_cavity = True
|
|
scene.display.shading.cavity_type = "BOTH"
|
|
scene.display.shading.curvature_ridge_factor = 1.5
|
|
scene.display.shading.curvature_valley_factor = 1.2
|
|
|
|
views = {
|
|
"iso": (1, -1, 0.8),
|
|
"front": (-1, 0, 0),
|
|
"back": (1, 0, 0),
|
|
"left": (0, -1, 0),
|
|
"right": (0, 1, 0),
|
|
"top": (0, 0, 1),
|
|
}
|
|
selected = requested_views or list(views)
|
|
unknown = sorted(set(selected) - set(views))
|
|
if unknown:
|
|
raise SystemExit(f"unknown views: {', '.join(unknown)}")
|
|
for name in selected:
|
|
direction = views[name]
|
|
render_view(camera, center, span, name, direction, output_dir, basename)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|