Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8292682ac2 |
@@ -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
|
## Baking for Render Farms
|
||||||
|
|
||||||
The **Bake & Export** button (bottom of the Pr3tz panel) converts the procedural animation into a standalone `.blend`:
|
The **Bake & Export** button (bottom of the Pr3tz panel) converts the procedural animation into a standalone `.blend`:
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
+2
-2
@@ -149,8 +149,8 @@ class KNOT_OT_BakePreview(bpy.types.Operator):
|
|||||||
glob = context.scene.knot_globals
|
glob = context.scene.knot_globals
|
||||||
_make_torus_knot(
|
_make_torus_knot(
|
||||||
config,
|
config,
|
||||||
resolution=glob.resolution,
|
resolution=glob.preview_resolution,
|
||||||
bevel_resolution=glob.bevel_resolution,
|
bevel_resolution=glob.preview_bevel_resolution,
|
||||||
knot_scale=glob.knot_scale,
|
knot_scale=glob.knot_scale,
|
||||||
scene=context.scene,
|
scene=context.scene,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user