Compare commits
3 commits
e4bcfdc7a5
...
b2ca9bcc32
| Author | SHA1 | Date | |
|---|---|---|---|
| b2ca9bcc32 | |||
| 6e3137b30a | |||
| 7c55fb8f80 |
BIN
blender/colorpie_template.blend
Normal file
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 163 KiB |
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
|
||||