colorpie.blend now has packed lighting and served as the master for the template and at this time there is no difference between the two. Confirmed webp creation, the results of which are being committed herein. Placed failed or poorly designed scripts in the attic for reference, with Google Gemini it started down a path of exporting a movie and then extracting &etc nonsense, that is when I returned to ChatGPT and we built render_colorpie_glb_to_webp.py
BIN
img/colorpie_gamut_20260308_1303_frame001_20260308_1759.webp
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
img/colorpie_gamut_20260308_1303_frame144_20260308_1759.webp
Normal file
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 51 KiB |
BIN
img/colorpie_muted_20260308_12303_frame001_20260308_1759.webp
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
img/colorpie_muted_20260308_12303_frame144_20260308_1759.webp
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
img/colorpie_voronish_20260308_12303_frame001_20260308_1758.webp
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
img/colorpie_voronish_20260308_12303_frame144_20260308_1758.webp
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
img/colorpie_warmcool_20260308_12303_frame001_20260308_1759.webp
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
img/colorpie_warmcool_20260308_12303_frame144_20260308_1759.webp
Normal file
|
After Width: | Height: | Size: 74 KiB |
112
scripts/attic/extract_webp.py
Normal 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)
|
||||
|
||||
284
scripts/attic/render_colorpie_glb_to_webp.py
Normal 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
|
|
@ -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
|
||||