From 8292682ac2b13c42e861f406ea8487045eb70b84 Mon Sep 17 00:00:00 2001 From: Stefan Cepko Date: Fri, 5 Jun 2026 05:36:50 -0400 Subject: [PATCH] Add example scripts for major functions --- README.md | 27 +++++ examples/01_simple_torus_knot.py | 72 +++++++++++++ examples/02_multi_link_knot.py | 70 +++++++++++++ examples/03_shape_types.py | 89 +++++++++++++++++ examples/04_animation_styles.py | 100 +++++++++++++++++++ examples/05_custom_transitions.py | 104 +++++++++++++++++++ examples/06_audio_reactive_simulation.py | 122 +++++++++++++++++++++++ examples/07_headless_render.py | 93 +++++++++++++++++ examples/08_programmatic_bake_export.py | 84 ++++++++++++++++ pr3tz/operators.py | 4 +- 10 files changed, 763 insertions(+), 2 deletions(-) create mode 100644 examples/01_simple_torus_knot.py create mode 100644 examples/02_multi_link_knot.py create mode 100644 examples/03_shape_types.py create mode 100644 examples/04_animation_styles.py create mode 100644 examples/05_custom_transitions.py create mode 100644 examples/06_audio_reactive_simulation.py create mode 100644 examples/07_headless_render.py create mode 100644 examples/08_programmatic_bake_export.py diff --git a/README.md b/README.md index dea6464..62a3072 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,33 @@ blender --python run_script.py --- +## Programmatic Examples + +The `examples/` directory contains 8 different Python scripts demonstrating how to interact with the Pr3tz animation engine programmatically: + +1. [01_simple_torus_knot.py](examples/01_simple_torus_knot.py) — Demonstrates basic torus knot generation with customizable coprime topology parameters. +2. [02_multi_link_knot.py](examples/02_multi_link_knot.py) — Shows how to generate multi-link torus knots when $p$ and $q$ are not coprime, producing multiple separate interlocking curve loops. +3. [03_shape_types.py](examples/03_shape_types.py) — Shows generating and editing other shapes supported by the engine (Mobius Strip, 3D Lissajous, Spherical Spiral). +4. [04_animation_styles.py](examples/04_animation_styles.py) — Exercises the four different built-in animation modes (spin, revolution, vertical height breathing, and scale pulsation). +5. [05_custom_transitions.py](examples/05_custom_transitions.py) — Configures morph transitions and material cross-fading between completely different geometry types and shader presets. +6. [06_audio_reactive_simulation.py](examples/06_audio_reactive_simulation.py) — Simulates audio-reactive animation by programmatically keyframing global parameters and knot attenuverters. +7. [07_headless_render.py](examples/07_headless_render.py) — Sets up and renders a sequence of frames headlessly using Blender's background CLI. +8. [08_programmatic_bake_export.py](examples/08_programmatic_bake_export.py) — Invokes the bake/export operator programmatically to compile procedural curves into standalone mesh frames. + +To run any example from the command line: + +**PowerShell (Windows):** +```powershell +& "C:\Program Files\Blender Foundation\Blender 5.0\blender.exe" --python examples/01_simple_torus_knot.py +``` + +**CMD (Windows):** +```cmd +"C:\Program Files\Blender Foundation\Blender 5.0\blender.exe" --python examples/01_simple_torus_knot.py +``` + +--- + ## Baking for Render Farms The **Bake & Export** button (bottom of the Pr3tz panel) converts the procedural animation into a standalone `.blend`: diff --git a/examples/01_simple_torus_knot.py b/examples/01_simple_torus_knot.py new file mode 100644 index 0000000..24ff492 --- /dev/null +++ b/examples/01_simple_torus_knot.py @@ -0,0 +1,72 @@ +import sys +import os +import bpy + +# 1. Resolve path to include workspace root so pr3tz can be imported +dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if dir_path not in sys.path: + sys.path.append(dir_path) + +import pr3tz + +# 2. Register pr3tz addon +try: + if "pr3tz" in sys.modules: + import importlib + importlib.reload(pr3tz) + else: + import pr3tz + + pr3tz.register() + print("[Pr3tz] Registered successfully!") +except Exception as e: + print(f"[Pr3tz] Failed to register: {e}") + sys.exit(1) + +# 3. Configure the scene (camera, lights, timeline, etc.) +pr3tz.setup_scene() + +# 4. Clear existing playlist +scene = bpy.context.scene +scene.knot_list.clear() + +# 5. Add a new knot configuration +item = scene.knot_list.add() +item.name = "Trefoil Knot" +item.shape_type = 'TORUS_KNOT' + +# Topology (coprime p and q) +item.torus_p = 2 # Revolutions around the torus axis +item.torus_q = 3 # Spins around the torus tube + +# Dimensions +item.mode = 'MAJOR_MINOR' +item.torus_R = 2.0 # Distance from center to tube center +item.torus_r = 0.8 # Radius of the tube + +# Geometry +item.geo_bDepth = 0.08 # Bevel depth (thickness of the curve tube) + +# Material settings +item.material_mode = 'PRESET' +item.shader_id = 'GLOSS_BLUE' +item.preset_color = (0.2, 0.6, 1.0) +item.preset_roughness = 0.1 +item.preset_metallic = 0.2 + +# 6. Fit timeline so the knot plays +scene.knot_globals.frames_per_knot = 120 +bpy.ops.knot.fit_timeline() + +# 7. Force instant geometry generation for current frame +pr3tz.knot_frame_handler(scene) + +print("\n=====================================================================") +print("Example 1: Simple Torus Knot Loaded!") +print("=====================================================================") +print("Generated a 3D Trefoil Knot (p=2, q=3) using the 'GLOSS_BLUE' preset.") +print("Interact with it in Blender:") +print(" - Open the 'Pr3tz' N-Panel in the 3D Viewport sidebar.") +print(" - Change 'Revolutions (p)' or 'Spins (q)' to see the knot shape morph.") +print(" - Modify the 'Bevel Depth' to change its thickness.") +print("=====================================================================\n") diff --git a/examples/02_multi_link_knot.py b/examples/02_multi_link_knot.py new file mode 100644 index 0000000..bd88086 --- /dev/null +++ b/examples/02_multi_link_knot.py @@ -0,0 +1,70 @@ +import sys +import os +import bpy + +# 1. Resolve path to include workspace root so pr3tz can be imported +dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if dir_path not in sys.path: + sys.path.append(dir_path) + +import pr3tz + +# 2. Register pr3tz addon +try: + if "pr3tz" in sys.modules: + import importlib + importlib.reload(pr3tz) + else: + import pr3tz + + pr3tz.register() + print("[Pr3tz] Registered successfully!") +except Exception as e: + print(f"[Pr3tz] Failed to register: {e}") + sys.exit(1) + +# 3. Configure the scene (camera, lights, timeline, etc.) +pr3tz.setup_scene() + +# 4. Clear existing playlist +scene = bpy.context.scene +scene.knot_list.clear() + +# 5. Add a multi-link knot configuration +item = scene.knot_list.add() +item.name = "Interlocking Solomon Link" +item.shape_type = 'TORUS_KNOT' + +# Topology with GCD > 1 +item.torus_p = 4 # 4 revolutions +item.torus_q = 6 # 6 spins. gcd(4, 6) = 2 separate interlocking links +item.multiple_links = True # Enable rendering of all links as separate splines + +# Dimensions & Geometry +item.torus_R = 2.5 +item.torus_r = 1.0 +item.geo_bDepth = 0.05 + +# Use a bright emissive neon color +item.material_mode = 'PRESET' +item.shader_id = 'NEON_GLOW' +item.preset_color = (0.0, 1.0, 0.9) # Cyan +item.preset_emission_strength = 3.0 + +# 6. Fit timeline so the knot plays +scene.knot_globals.frames_per_knot = 120 +bpy.ops.knot.fit_timeline() + +# 7. Force instant geometry generation for current frame +pr3tz.knot_frame_handler(scene) + +print("\n=====================================================================") +print("Example 2: Multi-Link Knot Loaded!") +print("=====================================================================") +print("Generated a 3D Solomon Link (p=4, q=6) with multiple_links=True.") +print("This renders as 2 distinct interlocking/nested splines.") +print("Interact with it in Blender:") +print(" - Open the 'Pr3tz' N-Panel.") +print(" - Toggle 'Multiple Links' off/on to see the difference between") +print(" rendering a single loop vs. all mathematical links.") +print("=================================================================\n") diff --git a/examples/03_shape_types.py b/examples/03_shape_types.py new file mode 100644 index 0000000..7a7b9ec --- /dev/null +++ b/examples/03_shape_types.py @@ -0,0 +1,89 @@ +import sys +import os +import bpy + +# 1. Resolve path to include workspace root so pr3tz can be imported +dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if dir_path not in sys.path: + sys.path.append(dir_path) + +import pr3tz + +# 2. Register pr3tz addon +try: + if "pr3tz" in sys.modules: + import importlib + importlib.reload(pr3tz) + else: + import pr3tz + + pr3tz.register() + print("[Pr3tz] Registered successfully!") +except Exception as e: + print(f"[Pr3tz] Failed to register: {e}") + sys.exit(1) + +# 3. Configure the scene (camera, lights, timeline, etc.) +pr3tz.setup_scene() + +# 4. Clear existing playlist +scene = bpy.context.scene +scene.knot_list.clear() + +# ── 1. Mobius Strip Winding ── +mobius_item = scene.knot_list.add() +mobius_item.name = "Mobius Strip Boundary" +mobius_item.shape_type = 'MOBIUS' +mobius_item.mobius_twists = 3 # Odd twist count creates single continuous loop +mobius_item.mobius_width = 1.2 +mobius_item.torus_R = 2.5 # Uses major radius for ring dimension +mobius_item.geo_bDepth = 0.04 +mobius_item.material_mode = 'PRESET' +mobius_item.shader_id = 'METALLIC' +mobius_item.preset_color = (1.0, 0.78, 0.28) # Gold + +# ── 2. Lissajous 3D Curve ── +liss_item = scene.knot_list.add() +liss_item.name = "3D Lissajous Knot" +liss_item.shape_type = 'LISSAJOUS' +liss_item.liss_kx = 3 # X frequency +liss_item.liss_ky = 2 # Y frequency +liss_item.liss_kz = 5 # Z frequency +liss_item.liss_amp = 2.5 +liss_item.geo_bDepth = 0.06 +liss_item.material_mode = 'PRESET' +liss_item.shader_id = 'LAVA' +liss_item.preset_color = (1.0, 0.1, 0.0) + +# ── 3. Spherical Spiral (Loxodrome) ── +spiral_item = scene.knot_list.add() +spiral_item.name = "Spherical Spiral" +spiral_item.shape_type = 'SPIRAL' +spiral_item.spiral_turns = 16 +spiral_item.spiral_R = 2.2 +# Open spiral (not cyclic) +spiral_item.geo_bDepth = 0.05 +spiral_item.material_mode = 'PRESET' +spiral_item.shader_id = 'IRIDESCENT' + +# 5. Set globals and fit timeline +glob = scene.knot_globals +glob.frames_per_knot = 100 +bpy.ops.knot.fit_timeline() + +# 6. Force instant geometry generation for current frame +pr3tz.knot_frame_handler(scene) + +print("\n=====================================================================") +print("Example 3: Shape Types Playlist Loaded!") +print("=====================================================================") +print("Created a playlist containing:") +print(" 1. Mobius Strip Winding (Gold Metallic)") +print(" 2. 3D Lissajous Curve (Emissive Lava)") +print(" 3. Spherical Spiral (Iridescent Rainbow)") +print("=====================================================================") +print("Interact with it in Blender:") +print(" - Play the animation (Space) or scrub the timeline.") +print(" - Each shape will display in sequence.") +print(" - Adjust specific shape properties in the N-Panel sidebar.") +print("=====================================================================\n") diff --git a/examples/04_animation_styles.py b/examples/04_animation_styles.py new file mode 100644 index 0000000..efde8ea --- /dev/null +++ b/examples/04_animation_styles.py @@ -0,0 +1,100 @@ +import sys +import os +import bpy + +# 1. Resolve path to include workspace root so pr3tz can be imported +dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if dir_path not in sys.path: + sys.path.append(dir_path) + +import pr3tz + +# 2. Register pr3tz addon +try: + if "pr3tz" in sys.modules: + import importlib + importlib.reload(pr3tz) + else: + import pr3tz + + pr3tz.register() + print("[Pr3tz] Registered successfully!") +except Exception as e: + print(f"[Pr3tz] Failed to register: {e}") + sys.exit(1) + +# 3. Configure the scene (camera, lights, timeline, etc.) +pr3tz.setup_scene() + +# 4. Clear existing playlist +scene = bpy.context.scene +scene.knot_list.clear() + +# ── Style 1: Tube Spin Winding ── +# Animates the knot twisting around its own tube geometry. +item_spin = scene.knot_list.add() +item_spin.name = "Tube Spin Animation" +item_spin.shape_type = 'TORUS_KNOT' +item_spin.torus_p = 3 +item_spin.torus_q = 5 +item_spin.spin_phase_rate = 0.05 # Radians spin rate per frame +item_spin.material_mode = 'PRESET' +item_spin.shader_id = 'CARBON_FIBER' +item_spin.preset_color = (0.3, 0.3, 0.3) + +# ── Style 2: Orbital Revolution ── +# Animates the entire knot revolving/orbiting around the core torus axis. +item_orbit = scene.knot_list.add() +item_orbit.name = "Orbital Orbit Animation" +item_orbit.shape_type = 'TORUS_KNOT' +item_orbit.torus_p = 3 +item_orbit.torus_q = 5 +item_orbit.rev_phase_rate = 0.03 # Radians revolution rate per frame +item_orbit.material_mode = 'PRESET' +item_orbit.shader_id = 'GLASS' +item_orbit.preset_color = (0.7, 0.9, 1.0) + +# ── Style 3: Vertical Height Breathing ── +# Vertically stretches and squashes the knot structure. +item_breath = scene.knot_list.add() +item_breath.name = "Height Breathing Pulse" +item_breath.shape_type = 'TORUS_KNOT' +item_breath.torus_p = 3 +item_breath.torus_q = 5 +item_breath.height_rate = 0.06 # Sine frequency for height pulse +item_breath.material_mode = 'PRESET' +item_breath.shader_id = 'PLASMA_GLOW' +item_breath.preset_color = (1.0, 0.1, 0.7) + +# ── Style 4: Uniform Scale Pulsation ── +# Animates the overall scale of the knot using a sine multiplier. +item_scale = scene.knot_list.add() +item_scale.name = "Scale Pulsation" +item_scale.shape_type = 'TORUS_KNOT' +item_scale.torus_p = 3 +item_scale.torus_q = 5 +item_scale.scale_amplitude = 0.25 # Scale factor oscillation amplitude (+/- 25%) +item_scale.scale_rate = 0.08 # Frequency of scale pulsation +item_scale.material_mode = 'PRESET' +item_scale.shader_id = 'ZEBRA_STRIPES' + +# 5. Fit timeline +scene.knot_globals.frames_per_knot = 150 +bpy.ops.knot.fit_timeline() + +# 6. Force instant geometry generation for current frame +pr3tz.knot_frame_handler(scene) + +print("\n=====================================================================") +print("Example 4: Animation Styles Playlist Loaded!") +print("=====================================================================") +print("Created 4 separate entries showing off isolated animation rates:") +print(" 1. Tube Spin (Carbon Fiber) — spin_phase_rate=0.05") +print(" 2. Orbital Orbit (Glass) — rev_phase_rate=0.03") +print(" 3. Height Breathing (Plasma) — height_rate=0.06") +print(" 4. Scale Pulsation (Zebra) — scale_amplitude=0.25, scale_rate=0.08") +print("=====================================================================") +print("Interact with it in Blender:") +print(" - Play the animation (Space) to see how the rates animate.") +print(" - Adjust the global 'Reactivity' slider to scale these speeds.") +print("=====================================================================\n") diff --git a/examples/05_custom_transitions.py b/examples/05_custom_transitions.py new file mode 100644 index 0000000..2ed9421 --- /dev/null +++ b/examples/05_custom_transitions.py @@ -0,0 +1,104 @@ +import sys +import os +import bpy + +# 1. Resolve path to include workspace root so pr3tz can be imported +dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if dir_path not in sys.path: + sys.path.append(dir_path) + +import pr3tz + +# 2. Register pr3tz addon +try: + if "pr3tz" in sys.modules: + import importlib + importlib.reload(pr3tz) + else: + import pr3tz + + pr3tz.register() + print("[Pr3tz] Registered successfully!") +except Exception as e: + print(f"[Pr3tz] Failed to register: {e}") + sys.exit(1) + +# 3. Configure the scene (camera, lights, timeline, etc.) +pr3tz.setup_scene() + +# 4. Clear existing playlist +scene = bpy.context.scene +scene.knot_list.clear() + +# ── Knot 1: Trefoil (Torus Knot) with Lava Preset ── +item_a = scene.knot_list.add() +item_a.name = "Trefoil Knot" +item_a.shape_type = 'TORUS_KNOT' +item_a.torus_p = 2 +item_a.torus_q = 3 +item_a.torus_R = 2.0 +item_a.torus_r = 0.8 +item_a.geo_bDepth = 0.05 +item_a.material_mode = 'PRESET' +item_a.shader_id = 'LAVA' +item_a.preset_color = (1.0, 0.2, 0.0) +item_a.cycle_rate = 1.0 + +# ── Knot 2: 3D Lissajous with Glass Preset (Morphed using QUAD_IN_OUT) ── +# Triggers a transition morph when transitioning from item_a to item_b +item_b = scene.knot_list.add() +item_b.name = "Lissajous Morph Target" +item_b.shape_type = 'LISSAJOUS' +item_b.liss_kx = 3 +item_b.liss_ky = 2 +item_b.liss_kz = 4 +item_b.liss_amp = 2.2 +item_b.geo_bDepth = 0.08 +item_b.material_mode = 'PRESET' +item_b.shader_id = 'GLASS' +item_b.preset_color = (0.3, 0.8, 1.0) +item_b.preset_roughness = 0.0 +item_b.cycle_rate = 1.0 +item_b.transition_frames = 60 # Morphs over 60 frames +item_b.transition_easing = 'QUAD_IN_OUT' # Smooth ease-in, ease-out curve + +# ── Knot 3: Mobius Loop with Lichen Preset (Morphed using SMOOTHSTEP) ── +item_c = scene.knot_list.add() +item_c.name = "Mobius Morph Target" +item_c.shape_type = 'MOBIUS' +item_c.mobius_twists = 1 +item_c.mobius_width = 1.6 +item_c.torus_R = 2.4 +item_c.geo_bDepth = 0.04 +item_c.material_mode = 'PRESET' +item_c.shader_id = 'COPPER_PATINA' +item_c.preset_color = (0.1, 0.7, 0.5) +item_c.cycle_rate = 1.0 +item_c.transition_frames = 40 # Morphs over 40 frames +item_c.transition_easing = 'SMOOTHSTEP' # S-curve ease + +# 5. Set up playlist blend materials. +# MUST call this whenever modifying transitions or colors programmatically, +# so the shader node trees are rebuilt. +pr3tz.materials.prewarm_materials_and_blends(scene) + +# 6. Fit timeline +scene.knot_globals.frames_per_knot = 120 +bpy.ops.knot.fit_timeline() + +# 7. Force instant geometry generation for current frame +pr3tz.knot_frame_handler(scene) + +print("\n=====================================================================") +print("Example 5: Morph Transitions Playlist Loaded!") +print("=====================================================================") +print("Created 3 distinct shapes with custom transition settings:") +print(" - Trefoil Knot (Lava Preset)") +print(" - Lissajous (Glass Preset) morphs from Trefoil over 60 frames (Quad In/Out)") +print(" - Mobius Loop (Copper Patina) morphs from Lissajous over 40 frames (Smoothstep)") +print("=====================================================================") +print("Interact with it in Blender:") +print(" - Play the animation (Space).") +print(" - Watch how the geometries blend smoothly between shapes.") +print(" - Observe the material node network transition from Lava to Glass/Copper.") +print("=====================================================================\n") diff --git a/examples/06_audio_reactive_simulation.py b/examples/06_audio_reactive_simulation.py new file mode 100644 index 0000000..a3e4a82 --- /dev/null +++ b/examples/06_audio_reactive_simulation.py @@ -0,0 +1,122 @@ +import sys +import os +import math +import random +import bpy + +# 1. Resolve path to include workspace root so pr3tz can be imported +dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if dir_path not in sys.path: + sys.path.append(dir_path) + +import pr3tz + +# 2. Register pr3tz addon +try: + if "pr3tz" in sys.modules: + import importlib + importlib.reload(pr3tz) + else: + import pr3tz + + pr3tz.register() + print("[Pr3tz] Registered successfully!") +except Exception as e: + print(f"[Pr3tz] Failed to register: {e}") + sys.exit(1) + +# 3. Configure the scene (camera, lights, timeline, etc.) +pr3tz.setup_scene() + +# 4. Clear existing playlist +scene = bpy.context.scene +scene.knot_list.clear() + +# 5. Add a base knot configuration +item = scene.knot_list.add() +item.name = "Audio Reactive Knot" +item.shape_type = 'TORUS_KNOT' +item.torus_p = 3 +item.torus_q = 7 +item.torus_R = 2.0 +item.torus_r = 0.8 +item.geo_bDepth = 0.05 + +# We will modulate minor radius and bevel depth using attenuverters. +# These variables will act as targets for our keyframed modulators. +item.mod_torus_r = 0.0 # Starts flat +item.mod_geo_bDepth = 0.0 + +item.material_mode = 'PRESET' +item.shader_id = 'NEON_GLOW' +item.preset_color = (1.0, 0.0, 0.5) # Magenta +item.preset_emission_strength = 2.0 + +# 6. Fit timeline +scene.frame_start = 1 +scene.frame_end = 240 +scene.render.fps = 24 + +# Set up prewarmed materials +pr3tz.materials.prewarm_materials_and_blends(scene) + +# 7. Generate a simulated audio track (bass peaks on beats, high-hat noise) +# We will write keyframes to global properties to drive the animation. +glob = scene.knot_globals + +# Clear any existing animation on globals +if glob.animation_data: + glob.animation_data.clear() +if item.animation_data: + item.animation_data.clear() + +print("Simulating audio analysis and writing keyframes...") + +# Loop over the frames and compute simulated audio values +for frame in range(scene.frame_start, scene.frame_end + 1): + # Beat occurs every 24 frames (1 second at 24fps) + beat_timer = (frame - 1) % 24 + + # Bass amplitude: sharp spike decaying exponentially + bass_amp = math.exp(-beat_timer * 0.15) if beat_timer < 20 else 0.0 + + # High frequency noise (high-hats/snares) + random.seed(frame) # Deterministic per frame + treble_amp = 0.15 * random.random() if (frame % 8) < 3 else 0.02 + + # Total reactivity scales the spin/rates + glob.reactivity_factor = 1.0 + bass_amp * 4.0 + glob.keyframe_insert(data_path="reactivity_factor", frame=frame) + + # Bevel depth thickness spikes with the bass beat + glob.global_master_thickness = 1.0 + bass_amp * 0.8 + glob.keyframe_insert(data_path="global_master_thickness", frame=frame) + + # Emission spikes on treble highlights + glob.global_emission_multiplier = 1.0 + treble_amp * 20.0 + glob.keyframe_insert(data_path="global_emission_multiplier", frame=frame) + + # Turbulence (noise) peaks on beats + glob.global_turbulence = bass_amp * 0.25 + glob.keyframe_insert(data_path="global_turbulence", frame=frame) + + # Hue shifts continuously, jumping slightly on beats + glob.global_hue_shift = (frame * 0.002 + bass_amp * 0.05) % 1.0 + glob.keyframe_insert(data_path="global_hue_shift", frame=frame) + +# 8. Force instant geometry generation for current frame +pr3tz.knot_frame_handler(scene) + +print("\n=====================================================================") +print("Example 6: Audio-Reactive Simulation Loaded!") +print("=====================================================================") +print("Generated a 240-frame animation simulating audio beat sync:") +print(" - 'reactivity_factor' & 'global_turbulence' spike on bass beats (every 24 frames).") +print(" - 'global_master_thickness' swells on beats.") +print(" - 'global_emission_multiplier' flashes on high-frequency treble ticks.") +print(" - 'global_hue_shift' rotates colors over time.") +print("=====================================================================") +print("Interact with it in Blender:") +print(" - Press Space to play.") +print(" - Look at the Graph Editor to see the custom audio curves keyframed.") +print("=====================================================================\n") diff --git a/examples/07_headless_render.py b/examples/07_headless_render.py new file mode 100644 index 0000000..9632091 --- /dev/null +++ b/examples/07_headless_render.py @@ -0,0 +1,93 @@ +import sys +import os +import bpy + +# 1. Resolve path to include workspace root so pr3tz can be imported +dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if dir_path not in sys.path: + sys.path.append(dir_path) + +import pr3tz + +# 2. Register pr3tz addon +try: + if "pr3tz" in sys.modules: + import importlib + importlib.reload(pr3tz) + else: + import pr3tz + + pr3tz.register() + print("[Pr3tz] Registered successfully!") +except Exception as e: + print(f"[Pr3tz] Failed to register: {e}") + sys.exit(1) + +# 3. Configure the scene (camera, lights, timeline, etc.) +pr3tz.setup_scene() + +# 4. Clear existing playlist +scene = bpy.context.scene +scene.knot_list.clear() + +# 5. Add a simple spinning knot configuration +item = scene.knot_list.add() +item.name = "Render Example Knot" +item.shape_type = 'TORUS_KNOT' +item.torus_p = 2 +item.torus_q = 5 +item.torus_R = 2.0 +item.torus_r = 0.8 +item.geo_bDepth = 0.06 +item.spin_phase_rate = 0.02 + +item.material_mode = 'PRESET' +item.shader_id = 'NEON_GLOW' +item.preset_color = (0.0, 0.5, 1.0) # Neon blue +item.preset_emission_strength = 2.0 + +pr3tz.materials.prewarm_materials_and_blends(scene) + +# 6. Configure rendering for a super-fast headless export +scene.render.engine = 'CYCLES' +scene.cycles.samples = 4 # Very low samples for quick testing +scene.cycles.use_denoising = False + +# Render size (small for fast test rendering) +scene.render.resolution_x = 480 +scene.render.resolution_y = 270 +scene.render.resolution_percentage = 100 + +# Setup Output directory +output_dir = os.path.join(dir_path, "renders") +if not os.path.exists(output_dir): + os.makedirs(output_dir) + +# Set base filename +scene.render.filepath = os.path.join(output_dir, "frame_") +scene.render.image_settings.file_format = 'PNG' +scene.render.image_settings.color_mode = 'RGBA' + +# 7. Render a short range (10 frames) +start_frame = 1 +end_frame = 10 +print(f"\nHeadless Render Started. Rendering frames {start_frame} to {end_frame}...") + +for f in range(start_frame, end_frame + 1): + print(f"Rendering frame {f}/{end_frame}...") + scene.frame_set(f) # Triggers knot_frame_handler automatically + + # We construct the filepath for this frame manually to save it + scene.render.filepath = os.path.join(output_dir, f"frame_{f:04d}.png") + + # Execute render + bpy.ops.render.render(write_still=True) + +print(f"\nRendering complete! Output frames saved to: {output_dir}") + +print("\n=====================================================================") +print("Example 7: Headless Render Script Completed!") +print("=====================================================================") +print("You can run this script from your terminal headlessly using:") +print(" blender -b -P examples/07_headless_render.py") +print("=====================================================================\n") diff --git a/examples/08_programmatic_bake_export.py b/examples/08_programmatic_bake_export.py new file mode 100644 index 0000000..e898759 --- /dev/null +++ b/examples/08_programmatic_bake_export.py @@ -0,0 +1,84 @@ +import sys +import os +import bpy + +# 1. Resolve path to include workspace root so pr3tz can be imported +dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if dir_path not in sys.path: + sys.path.append(dir_path) + +import pr3tz + +# 2. Register pr3tz addon +try: + if "pr3tz" in sys.modules: + import importlib + importlib.reload(pr3tz) + else: + import pr3tz + + pr3tz.register() + print("[Pr3tz] Registered successfully!") +except Exception as e: + print(f"[Pr3tz] Failed to register: {e}") + sys.exit(1) + +# 3. Configure the scene (camera, lights, timeline, etc.) +pr3tz.setup_scene() + +# 4. Clear existing playlist and populate with a couple of animated shapes +scene = bpy.context.scene +scene.knot_list.clear() + +item_1 = scene.knot_list.add() +item_1.name = "Bake Target A" +item_1.shape_type = 'TORUS_KNOT' +item_1.torus_p = 2 +item_1.torus_q = 3 +item_1.spin_phase_rate = 0.05 +item_1.material_mode = 'PRESET' +item_1.shader_id = 'LAVA' + +item_2 = scene.knot_list.add() +item_2.name = "Bake Target B" +item_2.shape_type = 'LISSAJOUS' +item_2.liss_kx = 3 +item_2.liss_ky = 2 +item_2.liss_amp = 2.0 +item_2.material_mode = 'PRESET' +item_2.shader_id = 'GLASS' +item_2.transition_frames = 20 + +pr3tz.materials.prewarm_materials_and_blends(scene) + +# 5. Set a short frame range for rapid baking +scene.frame_start = 1 +scene.frame_end = 40 +scene.render.fps = 24 + +# Setup target filepath for the baked blend file +baked_dir = os.path.join(dir_path, "baked") +if not os.path.exists(baked_dir): + os.makedirs(baked_dir) +output_blend = os.path.join(baked_dir, "baked_knot_animation.blend") + +# 6. Execute programmatic bake and export +print(f"\nStarting programmatic bake & export to: {output_blend}...") +# Run the operator directly, passing parameters. +# Since it inherits from ExportHelper, it expects filepath to be passed. +bpy.ops.knot.bake_export( + filepath=output_blend, + use_render_resolution=False, # Set to False to bake quickly at preview res + pack_textures=True, + split_export=False +) + +print(f"\nBake completed! stand-alone .blend file saved at: {output_blend}") + +print("\n=====================================================================") +print("Example 8: Programmatic Bake & Export Script Completed!") +print("=====================================================================") +print("The procedural knot was successfully baked to standalone meshes.") +print("The output .blend file requires no addons and can be rendered") +print("on any computer or render farm (e.g. SheepIt).") +print("=====================================================================\n") diff --git a/pr3tz/operators.py b/pr3tz/operators.py index 74b90c7..ad0963f 100644 --- a/pr3tz/operators.py +++ b/pr3tz/operators.py @@ -149,8 +149,8 @@ class KNOT_OT_BakePreview(bpy.types.Operator): glob = context.scene.knot_globals _make_torus_knot( config, - resolution=glob.resolution, - bevel_resolution=glob.bevel_resolution, + resolution=glob.preview_resolution, + bevel_resolution=glob.preview_bevel_resolution, knot_scale=glob.knot_scale, scene=context.scene, )