0.9.4
feat: large-room blood-pool solids, dash gore burst, GLB weapon refs
This commit is contained in:
1 parent
988e04b2d4
commit
2cf8dc89fe
9 files changed
+325
-13
No files matched your search
@@ -390,6 +390,12 @@ def reference_payload(name: str) -> list[dict]:
|
||||
if not REFERENCE.exists():
|
||||
raise SystemExit(f"Missing style reference: {REFERENCE}")
|
||||
references = [{"type": "image_url", "image_url": {"url": data_url(REFERENCE)}}]
|
||||
if name.startswith(("w_", "wt_", "pt_")):
|
||||
mid = name[3:] if name.startswith(("wt_", "pt_")) else name[2:]
|
||||
prefix = "wt_" if name.startswith(("wt_", "pt_")) else "w_"
|
||||
model = RAW / "guides" / "models" / f"{prefix}{mid}.png"
|
||||
if model.exists():
|
||||
references.append({"type": "image_url", "image_url": {"url": data_url(model)}})
|
||||
guide = shape_guide(name)
|
||||
if guide:
|
||||
references.append({"type": "image_url", "image_url": {"url": data_url(guide)}})
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render Meshy weapon GLBs to 2D magenta-backed stills via Blender."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
BLENDER_SCRIPT = Path(__file__).resolve().parent / "render_model_refs_blender.py"
|
||||
IN_DIR = ROOT / "assets" / "raw" / "models"
|
||||
OUT_DIR = ROOT / "assets" / "raw" / "guides" / "models"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
blender = shutil.which("blender")
|
||||
if not blender:
|
||||
print("blender: missing binary; skipping model reference renders")
|
||||
return 0
|
||||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
cmd = [
|
||||
blender,
|
||||
"--background",
|
||||
"--python",
|
||||
str(BLENDER_SCRIPT),
|
||||
"--",
|
||||
"--in",
|
||||
str(IN_DIR),
|
||||
"--out",
|
||||
str(OUT_DIR),
|
||||
]
|
||||
print(" ".join(cmd))
|
||||
return subprocess.call(cmd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Blender 5.2: orthographic stills of each weapon_<id>.glb."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
MAGENTA = (1.0, 0.0, 1.0, 1.0)
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--in", dest="in_dir", required=True)
|
||||
parser.add_argument("--out", dest="out_dir", required=True)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def reset_scene():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
scene = bpy.context.scene
|
||||
scene.render.engine = "BLENDER_EEVEE"
|
||||
scene.render.resolution_x = 1024
|
||||
scene.render.resolution_y = 1024
|
||||
scene.render.film_transparent = False
|
||||
scene.render.image_settings.file_format = "PNG"
|
||||
scene.render.image_settings.color_mode = "RGBA"
|
||||
world = bpy.data.worlds.new("Magenta")
|
||||
scene.world = world
|
||||
world.use_nodes = True
|
||||
bg = world.node_tree.nodes.get("Background")
|
||||
if bg:
|
||||
bg.inputs[0].default_value = MAGENTA
|
||||
bg.inputs[1].default_value = 1.0
|
||||
light_data = bpy.data.lights.new("Sun", "SUN")
|
||||
light_data.energy = 5.0
|
||||
light = bpy.data.objects.new("Sun", light_data)
|
||||
scene.collection.objects.link(light)
|
||||
light.location = (-1.0, -1.0, 2.0)
|
||||
light.rotation_euler = (math.atan2(math.hypot(1, 1), 2), 0.0, math.atan2(-1, -1))
|
||||
cam_data = bpy.data.cameras.new("Cam")
|
||||
cam_data.type = "ORTHO"
|
||||
cam_data.ortho_scale = 2.4
|
||||
cam = bpy.data.objects.new("Cam", cam_data)
|
||||
scene.collection.objects.link(cam)
|
||||
scene.camera = cam
|
||||
return scene, cam
|
||||
|
||||
|
||||
def import_weapon(path: Path):
|
||||
before = set(bpy.data.objects)
|
||||
bpy.ops.import_scene.gltf(filepath=str(path))
|
||||
imported = [obj for obj in bpy.data.objects if obj not in before]
|
||||
meshes = [obj for obj in imported if obj.type == "MESH"]
|
||||
if not meshes:
|
||||
return None
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for obj in meshes:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = meshes[0]
|
||||
if len(meshes) > 1:
|
||||
bpy.ops.object.join()
|
||||
joined = bpy.context.view_layer.objects.active
|
||||
return joined
|
||||
|
||||
|
||||
def normalize_weapon(obj):
|
||||
bpy.context.view_layer.update()
|
||||
bbox = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
|
||||
min_c = Vector((min(v.x for v in bbox), min(v.y for v in bbox), min(v.z for v in bbox)))
|
||||
max_c = Vector((max(v.x for v in bbox), max(v.y for v in bbox), max(v.z for v in bbox)))
|
||||
size = max_c - min_c
|
||||
longest = max(size.x, size.y, size.z, 1e-6)
|
||||
scale = 2.0 / longest
|
||||
obj.scale *= scale
|
||||
bpy.context.view_layer.update()
|
||||
bbox = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
|
||||
center = sum(bbox, Vector()) / 8.0
|
||||
obj.location -= center
|
||||
bpy.context.view_layer.update()
|
||||
bbox = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
|
||||
min_c = Vector((min(v.x for v in bbox), min(v.y for v in bbox), min(v.z for v in bbox)))
|
||||
max_c = Vector((max(v.x for v in bbox), max(v.y for v in bbox), max(v.z for v in bbox)))
|
||||
size = max_c - min_c
|
||||
axis = max(((size.x, "x"), (size.y, "y"), (size.z, "z")))[1]
|
||||
if axis == "y":
|
||||
obj.rotation_euler[2] += -math.pi / 2
|
||||
elif axis == "z":
|
||||
obj.rotation_euler[1] += math.pi / 2
|
||||
bpy.context.view_layer.update()
|
||||
bbox = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
|
||||
center = sum(bbox, Vector()) / 8.0
|
||||
obj.location -= center
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
|
||||
def aim_camera(cam, location):
|
||||
cam.location = location
|
||||
direction = Vector((0.0, 0.0, 0.0)) - Vector(location)
|
||||
cam.rotation_euler = direction.to_track_quat("-Z", "Y").to_euler()
|
||||
|
||||
|
||||
def render_to(scene, path: Path):
|
||||
scene.render.filepath = str(path)
|
||||
bpy.ops.render.render(write_still=True)
|
||||
|
||||
|
||||
def main(argv):
|
||||
args = parse_args(argv)
|
||||
in_dir = Path(args.in_dir)
|
||||
out_dir = Path(args.out_dir)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
glbs = sorted(in_dir.glob("weapon_*.glb"))
|
||||
for glb in glbs:
|
||||
wid = glb.stem[len("weapon_") :]
|
||||
scene, cam = reset_scene()
|
||||
obj = import_weapon(glb)
|
||||
if obj is None:
|
||||
print(f"skip {glb.name}: no mesh")
|
||||
continue
|
||||
normalize_weapon(obj)
|
||||
aim_camera(cam, (0.0, 0.0, 4.0))
|
||||
render_to(scene, out_dir / f"wt_{wid}.png")
|
||||
aim_camera(cam, (0.0, -3.2, 1.4))
|
||||
render_to(scene, out_dir / f"w_{wid}.png")
|
||||
print(f"rendered {wid}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
argv = sys.argv
|
||||
if "--" in argv:
|
||||
argv = argv[argv.index("--") + 1 :]
|
||||
else:
|
||||
argv = []
|
||||
main(argv)
|
||||
Reference in new issue
Block a user