diff --git a/blender/colorpie.blend b/blender/colorpie.blend index 7db5559..ccf3d37 100644 Binary files a/blender/colorpie.blend and b/blender/colorpie.blend differ diff --git a/img/colorpie_gamut_20260308_1303_frame001_20260308_1759.webp b/img/colorpie_gamut_20260308_1303_frame001_20260308_1759.webp new file mode 100644 index 0000000..9a5eb46 Binary files /dev/null and b/img/colorpie_gamut_20260308_1303_frame001_20260308_1759.webp differ diff --git a/img/colorpie_gamut_20260308_1303_frame144_20260308_1759.webp b/img/colorpie_gamut_20260308_1303_frame144_20260308_1759.webp new file mode 100644 index 0000000..790813e Binary files /dev/null and b/img/colorpie_gamut_20260308_1303_frame144_20260308_1759.webp differ diff --git a/img/colorpie_grayscale_20260308_12303_frame001_20260308_1758.webp b/img/colorpie_grayscale_20260308_12303_frame001_20260308_1758.webp new file mode 100644 index 0000000..5eecd07 Binary files /dev/null and b/img/colorpie_grayscale_20260308_12303_frame001_20260308_1758.webp differ diff --git a/img/colorpie_grayscale_20260308_12303_frame144_20260308_1758.webp b/img/colorpie_grayscale_20260308_12303_frame144_20260308_1758.webp new file mode 100644 index 0000000..78df93a Binary files /dev/null and b/img/colorpie_grayscale_20260308_12303_frame144_20260308_1758.webp differ diff --git a/img/colorpie_muted_20260308_12303_frame001_20260308_1759.webp b/img/colorpie_muted_20260308_12303_frame001_20260308_1759.webp new file mode 100644 index 0000000..9009fa9 Binary files /dev/null and b/img/colorpie_muted_20260308_12303_frame001_20260308_1759.webp differ diff --git a/img/colorpie_muted_20260308_12303_frame144_20260308_1759.webp b/img/colorpie_muted_20260308_12303_frame144_20260308_1759.webp new file mode 100644 index 0000000..4bd2ea7 Binary files /dev/null and b/img/colorpie_muted_20260308_12303_frame144_20260308_1759.webp differ diff --git a/img/colorpie_voronish_20260308_12303_frame001_20260308_1758.webp b/img/colorpie_voronish_20260308_12303_frame001_20260308_1758.webp new file mode 100644 index 0000000..28d5305 Binary files /dev/null and b/img/colorpie_voronish_20260308_12303_frame001_20260308_1758.webp differ diff --git a/img/colorpie_voronish_20260308_12303_frame144_20260308_1758.webp b/img/colorpie_voronish_20260308_12303_frame144_20260308_1758.webp new file mode 100644 index 0000000..028b187 Binary files /dev/null and b/img/colorpie_voronish_20260308_12303_frame144_20260308_1758.webp differ diff --git a/img/colorpie_warmcool_20260308_12303_frame001_20260308_1759.webp b/img/colorpie_warmcool_20260308_12303_frame001_20260308_1759.webp new file mode 100644 index 0000000..88e29c5 Binary files /dev/null and b/img/colorpie_warmcool_20260308_12303_frame001_20260308_1759.webp differ diff --git a/img/colorpie_warmcool_20260308_12303_frame144_20260308_1759.webp b/img/colorpie_warmcool_20260308_12303_frame144_20260308_1759.webp new file mode 100644 index 0000000..089ff71 Binary files /dev/null and b/img/colorpie_warmcool_20260308_12303_frame144_20260308_1759.webp differ diff --git a/scripts/attic/extract_webp.py b/scripts/attic/extract_webp.py new file mode 100644 index 0000000..8ba53bf --- /dev/null +++ b/scripts/attic/extract_webp.py @@ -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) + diff --git a/scripts/attic/render_colorpie_glb_to_webp.py b/scripts/attic/render_colorpie_glb_to_webp.py new file mode 100644 index 0000000..69a43de --- /dev/null +++ b/scripts/attic/render_colorpie_glb_to_webp.py @@ -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() diff --git a/scripts/render_multiple_glb.sh b/scripts/render_multiple_glb.sh new file mode 100755 index 0000000..392d37f --- /dev/null +++ b/scripts/render_multiple_glb.sh @@ -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 \ No newline at end of file