Initial parametric Feather case generator
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user