"""Release-art model renders. Uses existing meshes, no image generation.""" import bpy, bmesh, json, math from pathlib import Path from mathutils import Vector work=Path(__file__).parent out=work.parent/'outputs/FunctionalTurrets-release-art' out.mkdir(parents=True,exist_ok=True) def linear(x):return x/12.92 if x<=0.04045 else ((x+0.055)/1.055)**2.4 def rgba(hex):return tuple(linear(int(hex[i:i+2],16)/255) for i in (0,2,4))+(1,) for kind in ['lav','ferret']: bpy.ops.wm.open_mainfile(filepath=str(work/('lav_turret_rig.blend' if kind=='lav' else 'ferret_turret_rig.blend'))) rig=bpy.data.objects['KLT_LAVSkeleton' if kind=='lav' else 'KFT_FerretSkeleton'] rig.animation_data_clear() for bone in rig.pose.bones:bone.matrix_basis.identity() upper=bpy.data.objects['KLT_LAVTurret' if kind=='lav' else 'KFT_FerretTurret'] keep=({'KLT_LAVTurret','KLT_LAVHatch','KLT_LAVViewports','KLT_LAVRadio','KLT_LAVInterior'} if kind=='lav' else {'KFT_FerretTurret','KFT_FerretHatch'}) for obj in list(bpy.context.scene.objects): if obj.type=='MESH' and obj.name not in keep:bpy.data.objects.remove(obj,do_unlink=True) group=upper.vertex_groups['body_bone'].index fixed={v.index for v in upper.data.vertices if any(g.group==group and g.weight>.99 for g in v.groups)} faces={p.index for p in upper.data.polygons if all(i in fixed for i in p.vertices)} assert len(faces)==(52 if kind=='lav' else 16) bm=bmesh.new();bm.from_mesh(upper.data);bm.faces.ensure_lookup_table() bmesh.ops.delete(bm,geom=[f for f in bm.faces if f.index in faces],context='FACES');bm.to_mesh(upper.data);bm.free() mat=bpy.data.materials.new('HRSys distressed coral');mat.use_nodes=True nodes=mat.node_tree.nodes;links=mat.node_tree.links;bsdf=nodes.get('Principled BSDF') noise=nodes.new('ShaderNodeTexNoise');noise.inputs['Scale'].default_value=18;noise.inputs['Detail'].default_value=3.5;noise.inputs['Roughness'].default_value=.75 coords=nodes.new('ShaderNodeTexCoord');links.new(coords.outputs['Generated'],noise.inputs['Vector']) ramp=nodes.new('ShaderNodeValToRGB');ramp.color_ramp.elements[0].position=.22;ramp.color_ramp.elements[0].color=rgba('3e3330') ramp.color_ramp.elements[1].position=.46;ramp.color_ramp.elements[1].color=rgba('e46060') links.new(noise.outputs['Fac'],ramp.inputs[0]);links.new(ramp.outputs['Color'],bsdf.inputs['Base Color']) bsdf.inputs['Roughness'].default_value=.84;bsdf.inputs['Metallic'].default_value=.08 bump=nodes.new('ShaderNodeBump');bump.inputs['Strength'].default_value=.10;bump.inputs['Distance'].default_value=.025;links.new(noise.outputs['Fac'],bump.inputs['Height']);links.new(bump.outputs['Normal'],bsdf.inputs['Normal']) for obj in bpy.context.scene.objects: if obj.type=='MESH':obj.data.materials.clear();obj.data.materials.append(mat) scene=bpy.context.scene;scene.render.engine='CYCLES';scene.cycles.samples=96;scene.cycles.use_denoising=True scene.render.film_transparent=True;scene.render.image_settings.file_format='PNG';scene.render.image_settings.color_mode='RGBA';scene.render.image_settings.color_depth='8' scene.view_settings.view_transform='Standard';scene.view_settings.look='None';scene.view_settings.exposure=-.15 scene.world.use_nodes=True;scene.world.node_tree.nodes['Background'].inputs[0].default_value=(.72,.77,.72,1);scene.world.node_tree.nodes['Background'].inputs[1].default_value=.32 scene.render.resolution_x=1800;scene.render.resolution_y=1800;scene.render.resolution_percentage=100 bpy.context.view_layer.update() points=[o.matrix_world@Vector(c) for o in scene.objects if o.type=='MESH' for c in o.bound_box] low=Vector(tuple(min(v[a] for v in points) for a in range(3)));high=Vector(tuple(max(v[a] for v in points) for a in range(3)));centre=(low+high)/2 size=max(high-low) direction=Vector((.70,1,.67)) if kind=='lav' else Vector((-.8,1,.70)) bpy.ops.object.camera_add(location=centre+direction.normalized()*size*2.5) cam=bpy.context.object;cam.rotation_euler=(centre-cam.location).to_track_quat('-Z','Y').to_euler();cam.data.type='ORTHO';scene.camera=cam rot=cam.rotation_euler.to_matrix();projected=[rot.transposed()@(v-centre) for v in points] span=[max(p[a] for p in projected)-min(p[a] for p in projected) for a in range(2)] midpoint=Vector(tuple((max(p[a] for p in projected)+min(p[a] for p in projected))/2 for a in range(2))+(0,)) cam.location+=rot@midpoint;cam.data.ortho_scale=max(span)*1.08 def light(name,position,energy,scale): data=bpy.data.lights.new(name,'AREA');data.energy=energy*size*size;data.size=scale*size obj=bpy.data.objects.new(name,data);scene.collection.objects.link(obj);obj.location=centre+Vector(position)*size;obj.rotation_euler=(centre-obj.location).to_track_quat('-Z','Y').to_euler() light('Warm key',(-.6,.8,1.3),10,.65);light('Soft fill',(.8,.3,.5),3,.8);light('Rim',(.5,-1,1.1),13,.5) scene.render.filepath=str(out/f'{kind}-turret-render.png');bpy.ops.render.render(write_still=True) bpy.ops.wm.save_as_mainfile(filepath=str(out/f'{kind}-release-render.blend')) print('RELEASE_RENDER',kind,out)