Compare commits

...

3 commits

26 changed files with 417 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View file

@ -0,0 +1,112 @@
#!/usr/bin/python
#
# Google Gemini 3/8/26
#
import bpy
import os
import sys
# 1. Handle Command Line Arguments
argv = sys.argv
if "--" not in argv:
print("Usage: blender -b file.blend -P script.py -- model.glb")
sys.exit()
glb_path = argv[argv.index("--") + 1:]
if not glb_path:
sys.exit()
glb_path = glb_path[0]
base_name = os.path.splitext(os.path.basename(glb_path))[0]
# 2. Preparation: Find the Parent and Clear Old Children
parent = bpy.data.objects.get("ColorpieParent")
# Delete old children of the parent so we don't render multiple donuts on top of each other
if parent:
for child in parent.children:
bpy.data.objects.remove(child, do_unlink=True)
# 3. Import the New GLB
bpy.ops.import_scene.gltf(filepath=glb_path)
# 4. Parent New Objects to the Animation "Coin"
# Newly imported objects are automatically 'selected'
imported_objs = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']
for obj in imported_objs:
if parent:
obj.parent = parent
# Optional: Reset local transforms so it aligns perfectly with the parent
obj.location = (0, 0, 0)
# # 5. Render Settings
# scene = bpy.context.scene
# scene.render.image_settings.file_format = 'WEBP'
# scene.render.image_settings.quality = 95
# scene.render.film_transparent = False # Ensure black background from World nodes
# # 6. Render the "Stills" (Front and Back)
# # Front Face (1 second in)
# scene.frame_set(1)
# scene.render.filepath = os.path.abspath(f"front_{base_name}.webp")
# bpy.ops.render.render(write_still=True)
# # Back Face (Post-flip, e.g., Frame 144)
# scene.frame_set(144)
# scene.render.filepath = os.path.abspath(f"back_{base_name}.webp")
# bpy.ops.render.render(write_still=True)
# # 5. Render Settings (FFMPEG Workaround)
# scene = bpy.context.scene
# scene.render.image_settings.file_format = 'FFMPEG'
# scene.render.ffmpeg.format = 'QUICKTIME' # MOV container
# scene.render.ffmpeg.video_codec = 'PNG' # Use PNG compression inside the MOV
# 5. Render Settings (Blender 5.0 + Headless Workaround)
scene = bpy.context.scene
scene.render.image_settings.file_format = 'FFMPEG'
# Set the Container to Quicktime (MOV)
scene.render.ffmpeg.format = 'QUICKTIME'
# In Blender 5.0, use .codec instead of .video_codec
scene.render.ffmpeg.codec = 'PNG'
# Ensure it captures only the frame we want
scene.render.ffmpeg.constant_rate_factor = 'HIGH'
# # 6. Render the "Stills"
# # Note: Blender will add frame numbers to these filenames
# # Frame 1 (Front)
# scene.frame_set(1)
# scene.render.filepath = os.path.abspath(f"front_{base_name}")
# bpy.ops.render.render(write_still=True)
# # Frame 144 (Back)
# scene.frame_set(144)
# scene.render.filepath = os.path.abspath(f"back_{base_name}")
# bpy.ops.render.render(write_still=True)
# 6. Render the "Stills" as 1-frame movies
# Frame 1 (Front)
scene.frame_start = 1
scene.frame_end = 1
scene.render.filepath = os.path.abspath(f"front_{base_name}")
bpy.ops.render.render(animation=True)
# Frame 144 (Back)
scene.frame_start = 144
scene.frame_end = 144
scene.render.filepath = os.path.abspath(f"back_{base_name}")
bpy.ops.render.render(animation=True)
# 7. Render Full Video (Only for specific files)
if "full_gamut" in base_name:
# Set to Video format for the animation
scene.render.image_settings.file_format = 'FFMPEG'
scene.render.ffmpeg.format = 'MPEG4'
scene.render.ffmpeg.codec = 'H264'
scene.render.filepath = os.path.abspath(f"anim_{base_name}.mp4")
bpy.ops.render.render(animation=True)

View file

@ -0,0 +1,284 @@
#!/usr/bin/env python3
# 20260308 ChatGPT
# $Header$
#
# Example:
# blender-bin-5.0.0 -b /path/to/colorpie.blend \
# --python scripts/render_colorpie_glb_to_webp.py -- \
# /path/to/glb/colorpie_gamut_20260308_1303.glb \
# --outdir /path/to/out
#
# Optional:
# --parent ColorpieParent
# --front-frame 1
# --back-frame 144
# --quality 95
#
# This script assumes:
# 1) the .blend already has the correct camera, Eevee, world, lights, compositor, etc.
# 2) ColorpieParent is the animated top object
# 3) the incoming GLB is centered the same way as the original one
import bpy
import os
import sys
import time
def die(msg, code=1):
print(f"ERROR: {msg}", file=sys.stderr)
sys.exit(code)
def log(msg):
print(msg, flush=True)
def parse_args(argv):
if "--" in argv:
argv = argv[argv.index("--") + 1:]
else:
argv = []
glb_path = None
outdir = "."
parent_name = "ColorpieParent"
front_frame = 1
back_frame = 144
quality = 95
i = 0
pos = []
while i < len(argv):
a = argv[i]
if a == "--outdir":
i += 1
if i >= len(argv):
die("Missing value after --outdir")
outdir = argv[i]
elif a == "--parent":
i += 1
if i >= len(argv):
die("Missing value after --parent")
parent_name = argv[i]
elif a == "--front-frame":
i += 1
if i >= len(argv):
die("Missing value after --front-frame")
front_frame = int(argv[i])
elif a == "--back-frame":
i += 1
if i >= len(argv):
die("Missing value after --back-frame")
back_frame = int(argv[i])
elif a == "--quality":
i += 1
if i >= len(argv):
die("Missing value after --quality")
quality = int(argv[i])
else:
pos.append(a)
i += 1
if not pos:
die("Usage: blender -b template.blend --python script.py -- input.glb [--outdir DIR]")
glb_path = pos[0]
if not os.path.exists(glb_path):
die(f"GLB not found: {glb_path}")
os.makedirs(outdir, exist_ok=True)
return {
"glb_path": glb_path,
"outdir": outdir,
"parent_name": parent_name,
"front_frame": front_frame,
"back_frame": back_frame,
"quality": quality,
}
def collect_descendants(obj):
out = []
stack = list(obj.children)
while stack:
cur = stack.pop()
out.append(cur)
stack.extend(cur.children)
return out
def delete_descendants(parent):
victims = collect_descendants(parent)
if not victims:
log("No existing children beneath parent; nothing to delete")
return
# Unlink carefully from children upward
for obj in sorted(victims, key=lambda o: len(o.children), reverse=True):
try:
bpy.data.objects.remove(obj, do_unlink=True)
except Exception as exc:
log(f"WARNING: failed removing {obj.name}: {exc}")
log(f"Deleted {len(victims)} descendant object(s) under {parent.name}")
def import_glb_and_get_new_objects(glb_path):
before = set(bpy.data.objects.keys())
res = bpy.ops.import_scene.gltf(filepath=glb_path)
if "FINISHED" not in res:
die(f"GLB import failed: {glb_path}")
after = set(bpy.data.objects.keys())
new_names = sorted(after - before)
new_objs = [bpy.data.objects[name] for name in new_names]
if not new_objs:
die("Import succeeded, but no new objects were detected")
log(f"Imported {len(new_objs)} object(s) from {glb_path}")
return new_objs
def get_import_roots(imported_objs):
imported_set = set(imported_objs)
roots = [obj for obj in imported_objs if obj.parent not in imported_set]
if not roots:
roots = imported_objs[:]
return roots
def parent_roots_to_target(roots, parent):
for obj in roots:
# Preserve current world-space transform on parenting
world_matrix = obj.matrix_world.copy()
obj.parent = parent
obj.matrix_parent_inverse = parent.matrix_world.inverted()
obj.matrix_world = world_matrix
log(f"Parented {len(roots)} imported root object(s) to {parent.name}")
# def ensure_render_settings(scene, quality):
# scene.render.image_settings.file_format = 'WEBP'
# scene.render.image_settings.quality = quality
# scene.render.use_file_extension = True
# # Leave all your scene-specific Eevee/world/compositor settings alone.
# # The point is to use the .blend as-is.
# log(f"Render format set to WEBP, quality={quality}")
def ensure_render_settings(scene, quality):
isettings = scene.render.image_settings
# Blender 5.x split still vs video by media_type.
# Do not guess blindly; inspect the enum and choose the still-image mode.
media_prop = isettings.bl_rna.properties.get("media_type")
if media_prop is not None:
media_keys = {item.identifier for item in media_prop.enum_items}
if "IMAGE" in media_keys:
isettings.media_type = 'IMAGE'
elif "STILL" in media_keys:
isettings.media_type = 'STILL'
elif "VIDEO" in media_keys and len(media_keys) == 1:
raise RuntimeError(
f"Scene is locked to VIDEO media_type only: {sorted(media_keys)}"
)
fmt_keys = {
item.identifier
for item in isettings.bl_rna.properties["file_format"].enum_items
}
# Prefer WEBP if available, otherwise fall back to PNG.
if "WEBP" in fmt_keys:
isettings.file_format = 'WEBP'
if hasattr(isettings, "quality"):
isettings.quality = quality
ext = ".webp"
elif "PNG" in fmt_keys:
isettings.file_format = 'PNG'
ext = ".png"
else:
raise RuntimeError(
f"No usable still-image format available. file_format choices: {sorted(fmt_keys)}"
)
scene.render.use_file_extension = True
print(f"media_type = {getattr(isettings, 'media_type', '(none)')}")
print(f"file_format choices = {sorted(fmt_keys)}")
print(f"using still format = {isettings.file_format}")
return ext
def render_frame(scene, frame_no, outfile):
scene.frame_set(frame_no)
scene.render.filepath = outfile
log(f"Rendering frame {frame_no} -> {outfile}")
bpy.ops.render.render(write_still=True)
def main():
cfg = parse_args(sys.argv)
glb_path = cfg["glb_path"]
outdir = cfg["outdir"]
parent_name = cfg["parent_name"]
front_frame = cfg["front_frame"]
back_frame = cfg["back_frame"]
quality = cfg["quality"]
base_name = os.path.splitext(os.path.basename(glb_path))[0]
timestamp = time.strftime("%Y%m%d_%H%M")
scene = bpy.context.scene
parent = bpy.data.objects.get(parent_name)
if parent is None:
die(f"Parent object not found: {parent_name}")
log(f"Using parent: {parent.name}")
log(f"Using scene: {scene.name}")
log(f"Input GLB: {glb_path}")
delete_descendants(parent)
imported = import_glb_and_get_new_objects(glb_path)
roots = get_import_roots(imported)
parent_roots_to_target(roots, parent)
for obj in imported:
pname = obj.parent.name if obj.parent else "-"
print(f"IMPORTED: {obj.name:30s} type={obj.type:8s} parent={pname}")
# ensure_render_settings(scene, quality)
# front_out = os.path.join(outdir, f"{base_name}_frame001_{timestamp}.webp")
# back_out = os.path.join(outdir, f"{base_name}_frame144_{timestamp}.webp")
ext = ensure_render_settings(scene, quality)
front_out = os.path.join(outdir, f"{base_name}_frame001_{timestamp}{ext}")
back_out = os.path.join(outdir, f"{base_name}_frame144_{timestamp}{ext}")
render_frame(scene, front_frame, front_out)
render_frame(scene, back_frame, back_out)
log("Done")
log(f"Wrote: {front_out}")
log(f"Wrote: {back_out}")
if __name__ == "__main__":
main()

21
scripts/render_multiple_glb.sh Executable file
View file

@ -0,0 +1,21 @@
#!/bin/bash
#
# Use this to mass process extraction of frames from several glb
# 3/8/2026
# ChatGPT
#
#
# This needs to be run from whitin colorpie/scripts directory
#
for glb in \
../glb/colorpie_voronish_20260308_12303.glb \
../glb/colorpie_grayscale_20260308_12303.glb \
../glb/colorpie_warmcool_20260308_12303.glb \
../glb/colorpie_muted_20260308_12303.glb \
../glb/colorpie_gamut_20260308_1303.glb
do
blender-bin-5.0.0 -b ../blender/colorpie_template.blend \
--python render_colorpie_frames.py -- \
"$glb" \
--outdir ../img --format webp
done