"""Grouper: quick Workbench looks of the built model (nothing saved). Exec'd in Blender after a build, or headless: blender.exe -b Tender_Grouper.blend --python build/gr_preview.py -- [ortho] [persp] [wire] [tag=NAME] ortho: the sheet's four views (SIDE, TOP, FRONT, BACK) rendered orthographically at 4 render px per sheet px, framed on the sheet's own view boxes (FRONT and BACK at the sheet's vertical scale; build/gr_compare_sheet.py stretches the sheet's narrower horizontal scale), -> previews/wb_.png persp: three-quarter looks and close-ups -> previews/wb_.png wire: the same perspective shots with every mesh's edges drawn -> previews/topo_.png Light preview colours (the materials' real look comes from the surfaces pass): gunmetal light grey, the band a shade darker, frames dark, strips cyan, glass pale blue (so the shape reads; the cabin is hidden behind it).""" import bpy, math, os, sys from mathutils import Vector, Matrix HERE = r"D:\HADAL - The Descent\models\tender-grouper\build" OUTD = os.path.join(os.path.dirname(HERE), "previews") os.makedirs(OUTD, exist_ok=True) args = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else list(globals().get("PREVIEW_ARGS", ["ortho", "persp"])) TAG = "" for a in args: if a.startswith("tag="): TAG = a[4:] + "_" if "Yi" not in globals(): exec(open(os.path.join(HERE, "gr_measure.py")).read()) sc = bpy.context.scene PREV = {"M_Grouper_Gunmetal": (0.56, 0.58, 0.61), "M_Grouper_Band": (0.40, 0.42, 0.45), "M_Grouper_FrameDark": (0.16, 0.17, 0.18), "M_Grouper_Strip": (0.15, 0.85, 1.0), "M_Grouper_Trace": (0.15, 0.85, 1.0), "M_Grouper_Acrylic": (0.70, 0.86, 0.95), "M_Grouper_LampWhite": (1.0, 1.0, 0.92), "M_Grouper_ScannerLens": (0.35, 0.65, 1.0), "M_Grouper_DuctGlow": (0.10, 0.55, 0.65), "M_Grouper_Recess": (0.06, 0.06, 0.06), "M_Grouper_Steel": (0.7, 0.72, 0.75)} saved = {} for m in bpy.data.materials: if m.name in PREV: saved[m.name] = tuple(m.diffuse_color) m.diffuse_color = PREV[m.name] + (1.0,) sc.render.engine = 'BLENDER_WORKBENCH' sh = sc.display.shading sh.light = 'STUDIO' sh.color_type = 'MATERIAL' sh.show_cavity = False sh.show_object_outline = True sh.object_outline_color = (0.0, 0.0, 0.0) sc.render.film_transparent = False sc.render.image_settings.file_format = 'PNG' sc.render.image_settings.color_mode = 'RGB' sc.render.resolution_percentage = 100 for o in bpy.data.objects: if o.name.startswith("UCX_") or o.name.startswith("SOCKET_"): o.hide_render = True cam = bpy.data.objects.new("_pv_cam", bpy.data.cameras.new("_pv_cam")) sc.collection.objects.link(cam) prev_cam = sc.camera sc.camera = cam done = [] def aim(loc, right, up): fwd = up.cross(right) # camera looks along local -Z; right = local +X, up = local +Y R = Matrix((right, up, -fwd)).transposed() cam.matrix_world = Matrix.Translation(loc) @ R.to_4x4() # ---- the sheet's four views: (box in sheet px, horizontal metres per sheet px, centre in the model, right, up, camera offset) K = 4 VIEWS = { "side": ((430, 255, 1010, 520), S, lambda cx, cy: Vector((-30.0, Yi(cx), Zi(cy))), Vector((0, -1, 0)), Vector((0, 0, 1))), "top": ((440, 20, 1000, 250), S, lambda cx, cy: Vector((Xt(cy), Yi(cx), 30.0)), Vector((0, -1, 0)), Vector((1, 0, 0))), "front": ((95, 215, 385, 550), S * KF, lambda cx, cy: Vector((Xf(cx), -30.0, Zi(cy))), Vector((1, 0, 0)), Vector((0, 0, 1))), "back": ((1075, 235, 1345, 550), S * KB, lambda cx, cy: Vector((Xb(cx), 30.0, Zi(cy))), Vector((-1, 0, 0)), Vector((0, 0, 1))), } if "ortho" in args: cam.data.type = 'ORTHO' cam.data.clip_start, cam.data.clip_end = 1.0, 80.0 for nm, ((x0, y0, x1, y1), mh, centre, right, up) in VIEWS.items(): w_px, h_px = x1 - x0, y1 - y0 # the render keeps the model's own (uniform) scale: S per sheet px vertically; horizontally the sheet's FRONT/BACK px # are narrower (KF, KB), so the render is wider than the sheet's box by S / mh rw = int(round(w_px * K * mh / S)) if nm in ("front", "back") else w_px * K rh = h_px * K sc.render.resolution_x, sc.render.resolution_y = rw, rh cam.data.sensor_fit = 'VERTICAL' cam.data.ortho_scale = h_px * S aim(centre(0.5 * (x0 + x1), 0.5 * (y0 + y1)), right, up) sc.render.filepath = os.path.join(OUTD, "%swb_%s.png" % (TAG, nm)) bpy.ops.render.render(write_still=True) done.append(sc.render.filepath) SHOTS = { "q_front_stbd": (Vector((-7.2, -7.4, 3.4)), Vector((0, -0.3, -0.1)), 34), "q_rear_port": (Vector((6.8, 7.6, 3.0)), Vector((0, 0.7, -0.1)), 34), "q_low_front": (Vector((-5.5, -7.5, -2.6)), Vector((0, -1.0, -0.6)), 34), "q_top": (Vector((-3.0, -1.5, 9.5)), Vector((0, 0.3, 0.3)), 34), "z_nose": (Vector((-3.2, -5.6, 1.2)), Vector((-0.2, -2.3, -0.1)), 36), "z_stern": (Vector((-3.2, 6.8, 1.0)), Vector((-0.2, 3.3, -0.1)), 36), "z_door": (Vector((-4.6, -0.6, 0.3)), Vector((-1.0, -0.2, -0.2)), 36), "z_collar": (Vector((-1.9, -1.6, 2.9)), Vector((0, -0.1, 1.25)), 38), } cam.data.type = 'PERSP' cam.data.clip_start, cam.data.clip_end = 0.05, 200.0 cam.data.sensor_fit = 'AUTO' modes = [m for m in ("persp", "wire") if m in args] for mode in modes: temps = [] wc = None if mode == "wire": wc = bpy.data.collections.new("_wire") sc.collection.children.link(wc) wm = bpy.data.materials.get("_wire_ink") or bpy.data.materials.new("_wire_ink") wm.diffuse_color = (0.02, 0.02, 0.03, 1) coll = bpy.data.collections.get("GROUPER | Tender (exported)") for o in coll.all_objects: if o.type != 'MESH' or o.name in ("SM_Grouper_Cabin",): continue t = o.copy(); t.data = o.data.copy(); t.name = "_w_" + o.name t.data.materials.clear(); t.data.materials.append(wm) t.parent = None; t.matrix_world = o.matrix_world.copy() for md_ in list(t.modifiers): t.modifiers.remove(md_) wc.objects.link(t) md = t.modifiers.new("w", 'WIREFRAME'); md.thickness = 0.0028; md.use_relative_offset = False; md.use_even_offset = False temps.append(t) sc.render.resolution_x, sc.render.resolution_y = (1800, 1100) if mode == "wire" else (1400, 860) for nm, (loc, tgt, lens) in SHOTS.items(): cam.location = loc cam.rotation_euler = (tgt - loc).normalized().to_track_quat('-Z', 'Y').to_euler() cam.data.lens = lens sc.render.filepath = os.path.join(OUTD, "%s%s_%s.png" % (TAG, "topo" if mode == "wire" else "wb", nm)) bpy.ops.render.render(write_still=True) done.append(sc.render.filepath) for t in temps: me = t.data bpy.data.objects.remove(t) bpy.data.meshes.remove(me) if wc is not None: bpy.data.collections.remove(wc) sc.camera = prev_cam bpy.data.objects.remove(cam) for m in bpy.data.materials: if m.name in saved: m.diffuse_color = saved[m.name] print("PREVIEW OK", len(done))