rename knot_animation folder

This commit is contained in:
Stefan Cepko
2026-06-04 06:04:45 -04:00
parent d6d6348ce6
commit b6d0eeffbb
12 changed files with 0 additions and 0 deletions
+424
View File
@@ -0,0 +1,424 @@
"""
ui.py
-----
All Blender UI classes for knot_animation:
KNOT_UL_List — playlist UIList
KNOT_UL_AllowedMaterialsList— generator filter UIList
draw_knot_properties() — shared property layout helper (collapsible sections)
KNOT_PT_Panel — main N-panel (VIEW_3D > UI > Pr3tz)
Every major block is collapsible via per-item or per-settings ui_show_* flags.
KnotItem flags are per-item so each playlist entry remembers its own state.
"""
from __future__ import annotations
import bpy
from .handler import compute_playlist_duration
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _section(layout, item, flag: str, label: str, icon: str = 'NONE'):
"""Draw a collapsible box section. Returns (box, is_open)."""
box = layout.box()
row = box.row()
row.prop(item, flag,
icon='TRIA_DOWN' if getattr(item, flag) else 'TRIA_RIGHT',
icon_only=True, emboss=False)
row.label(text=label, icon=icon)
return box, getattr(item, flag)
def _subsection(layout, item, flag: str, label: str, icon: str = 'NONE'):
"""Like _section but without an outer box — for nesting inside existing boxes."""
row = layout.row()
row.prop(item, flag,
icon='TRIA_DOWN' if getattr(item, flag) else 'TRIA_RIGHT',
icon_only=True, emboss=False)
row.label(text=label, icon=icon)
return getattr(item, flag)
def draw_prop_with_mod(layout, item, prop: str, mod_prop: str):
"""Draw a split row with the base property and its attenuverter (Mod)."""
split = layout.split(factor=0.6)
split.prop(item, prop)
split.prop(item, mod_prop)
# ---------------------------------------------------------------------------
# UILists
# ---------------------------------------------------------------------------
class KNOT_UL_List(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon,
active_data, active_propname, index):
layout.label(text=item.name, icon='CURVE_PATH')
class KNOT_UL_AllowedMaterialsList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon,
active_data, active_propname, index):
row = layout.row(align=True)
row.prop(item, "enabled", text="")
if item.is_preset:
row.label(text=f"Preset: {item.name}", icon='SHADING_RENDERED')
else:
row.label(text=f"Material: {item.name}", icon='MATERIAL')
# ---------------------------------------------------------------------------
# Shared property layout helper
# ---------------------------------------------------------------------------
def draw_knot_properties(layout, item) -> None:
"""Draw all KnotItem properties using collapsible per-item sections."""
# ── Shape ──────────────────────────────────────────────────────────────
box, open_ = _section(layout, item, "ui_show_shape", "Shape", 'CURVE_PATH')
if open_:
box.prop(item, "shape_type")
if item.shape_type == 'TORUS_KNOT':
draw_prop_with_mod(box, item, "torus_p", "mod_torus_p")
draw_prop_with_mod(box, item, "torus_q", "mod_torus_q")
row = box.row(align=True)
row.prop(item, "flip_p", toggle=True, icon='ARROW_LEFTRIGHT')
row.prop(item, "flip_q", toggle=True, icon='ARROW_LEFTRIGHT')
box.prop(item, "mode")
if item.mode == 'MAJOR_MINOR':
draw_prop_with_mod(box, item, "torus_R", "mod_torus_R")
draw_prop_with_mod(box, item, "torus_r", "mod_torus_r")
else:
draw_prop_with_mod(box, item, "torus_eR", "mod_torus_eR")
draw_prop_with_mod(box, item, "torus_iR", "mod_torus_iR")
elif item.shape_type == 'MOBIUS':
draw_prop_with_mod(box, item, "mobius_twists", "mod_mobius_twists")
draw_prop_with_mod(box, item, "mobius_width", "mod_mobius_width")
elif item.shape_type == 'LISSAJOUS':
draw_prop_with_mod(box, item, "liss_kx", "mod_liss_kx")
draw_prop_with_mod(box, item, "liss_ky", "mod_liss_ky")
draw_prop_with_mod(box, item, "liss_kz", "mod_liss_kz")
draw_prop_with_mod(box, item, "liss_amp", "mod_liss_amp")
elif item.shape_type == 'SPIRAL':
draw_prop_with_mod(box, item, "spiral_turns", "mod_spiral_turns")
draw_prop_with_mod(box, item, "spiral_R", "mod_spiral_R")
# ── Geometry ────────────────────────────────────────────────────────────
box, open_ = _section(layout, item, "ui_show_geometry", "Geometry", 'MESH_DATA')
if open_:
draw_prop_with_mod(box, item, "geo_extrude", "mod_geo_extrude")
draw_prop_with_mod(box, item, "geo_offset", "mod_geo_offset")
draw_prop_with_mod(box, item, "geo_bDepth", "mod_geo_bDepth")
draw_prop_with_mod(box, item, "torus_h", "mod_torus_h")
# ── Links & Phases ───────────────────────────────────────────────────────
box, open_ = _section(layout, item, "ui_show_links", "Links & Phases", 'LINKED')
if open_:
box.prop(item, "multiple_links")
col = box.column(align=True)
row = col.row(align=True)
row.prop(item, "torus_u")
row.prop(item, "torus_v")
row = col.row(align=True)
row.prop(item, "torus_rP")
row.prop(item, "torus_sP")
# ── Animation Rates ──────────────────────────────────────────────────────
box, open_ = _section(layout, item, "ui_show_anim", "Animation Rates", 'ANIM')
if open_:
box.prop(item, "cycle_rate")
col = box.column(align=True)
row = col.row(align=True)
row.prop(item, "spin_phase_rate")
row.prop(item, "rev_phase_rate")
row = col.row(align=True)
row.prop(item, "height_rate")
row = col.row(align=True)
row.prop(item, "scale_rate")
row.prop(item, "scale_amplitude")
# ── Material ─────────────────────────────────────────────────────────────
box, open_ = _section(layout, item, "ui_show_material", "Material", 'MATERIAL')
if open_:
box.prop(item, "material_mode", expand=True)
if item.material_mode == 'PRESET':
box.prop(item, "shader_id")
col = box.column(align=True)
col.prop(item, "preset_color")
row = col.row(align=True)
row.prop(item, "preset_roughness", slider=True)
row.prop(item, "preset_metallic", slider=True)
col.prop(item, "preset_emission_strength")
else:
box.prop(item, "project_material", text="Material")
# ── Colors ───────────────────────────────────────────────────────────────
box, open_ = _section(layout, item, "ui_show_colors", "Colors", 'COLOR')
if open_:
box.prop(item, "use_colors")
if item.use_colors:
row = box.row(align=True)
row.prop(item, "colorSet")
row.prop(item, "random_colors")
# ── Transition ───────────────────────────────────────────────────────────
box, open_ = _section(layout, item, "ui_show_trans", "Transition", 'MOD_TIME')
if open_:
box.prop(item, "transition_frames")
if item.transition_frames > 0:
box.prop(item, "transition_easing")
# ---------------------------------------------------------------------------
# Main panel
# ---------------------------------------------------------------------------
class KNOT_PT_Panel(bpy.types.Panel):
bl_label = "Pr3tz"
bl_idname = "KNOT_PT_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Pr3tz'
def draw(self, context):
layout = self.layout
scene = context.scene
glob = scene.knot_globals
gen = scene.knot_generator
# ── Playlist ─────────────────────────────────────────────────────────
pl_box = layout.box()
pl_row = pl_box.row()
pl_row.prop(glob, "ui_show_playlist",
icon='TRIA_DOWN' if glob.ui_show_playlist else 'TRIA_RIGHT',
icon_only=True, emboss=False)
pl_row.label(text="Playlist", icon='NLA')
if glob.ui_show_playlist:
row = pl_box.row()
row.template_list("KNOT_UL_List", "", scene, "knot_list",
scene, "knot_list_index")
col = row.column(align=True)
col.operator("knot.add_item", text="", icon='ADD')
col.operator("knot.remove_item", text="", icon='REMOVE')
col.separator()
col.operator("knot.move_item", text="", icon='TRIA_UP'
).direction = 'UP'
col.operator("knot.move_item", text="", icon='TRIA_DOWN'
).direction = 'DOWN'
pl_box.operator("knot.populate", icon='FILE_REFRESH')
# ── Selected Knot ─────────────────────────────────────────────────────
if 0 <= scene.knot_list_index < len(scene.knot_list):
item = scene.knot_list[scene.knot_list_index]
ke_box = layout.box()
ke_row = ke_box.row()
ke_row.prop(glob, "ui_show_knot_edit",
icon='TRIA_DOWN' if glob.ui_show_knot_edit else 'TRIA_RIGHT',
icon_only=True, emboss=False)
ke_row.label(text=f"Knot: {item.name}", icon='CURVE_PATH')
if glob.ui_show_knot_edit:
ke_box.prop(item, "name", text="",)
draw_knot_properties(ke_box, item)
ke_box.operator("knot.bake_preview", icon='FILE_TICK')
# ── Global Settings ───────────────────────────────────────────────────
gb = layout.box()
gb_row = gb.row()
gb_row.prop(glob, "ui_show_global",
icon='TRIA_DOWN' if glob.ui_show_global else 'TRIA_RIGHT',
icon_only=True, emboss=False)
gb_row.label(text="Global Settings", icon='WORLD')
if glob.ui_show_global:
gb.prop(glob, "knot_scale")
gb.prop(glob, "frames_per_knot")
# Resolutions
res_box = gb.box()
res_box.label(text="Resolutions (Preview / Render)", icon='RESTRICT_VIEW_OFF')
row = res_box.row(align=True)
row.prop(glob, "preview_resolution", text="Curve")
row.prop(glob, "render_resolution", text="")
row = res_box.row(align=True)
row.prop(glob, "preview_bevel_resolution", text="Bevel")
row.prop(glob, "render_bevel_resolution", text="")
# Visual Effects & Mods
fx_box = gb.box()
fx_box.label(text="Visual Effects & Tweaks", icon='MODIFIER')
fx_box.prop(glob, "global_master_thickness", slider=True)
fx_box.prop(glob, "global_emission_multiplier", slider=True)
fx_box.prop(glob, "global_hue_shift", slider=True)
fx_box.prop(glob, "global_turbulence", slider=True)
fx_box.prop(glob, "camera_shake_amplitude", slider=True)
fx_box.prop(glob, "auto_turntable_speed")
row = fx_box.row(align=True)
row.prop(glob, "smooth_shading", toggle=True, icon='SHADING_RENDERED')
row.prop(glob, "viewport_wireframe", toggle=True, icon='SHADING_WIRE')
n_knots = len(scene.knot_list)
if n_knots > 0:
total_frames = compute_playlist_duration(scene)
fps = max(1, scene.render.fps)
secs = total_frames / fps
gb.label(
text=f"Playlist: {total_frames} fr / {secs:.1f}s ({n_knots} knots)",
icon='TIME')
row = gb.row(align=True)
row.operator("knot.fit_timeline", icon='PREVIEW_RANGE', text="Fit Timeline")
row.operator("knot.fit_playlist", icon='NLA_PUSHDOWN', text="Fit Playlist")
# · Playback sub-section ·
gb.separator()
pb_open = _subsection(gb, glob, "ui_show_playback", "Playback", 'PLAY')
if pb_open:
pb = gb.column(align=True)
pb.prop(glob, "global_speed")
pb.prop(glob, "animation_phase")
pb.prop(glob, "reactivity_factor", slider=True)
gb.label(text="↑ Keyframe or drive for reactivity", icon='INFO')
row = gb.row(align=True)
row.prop(scene, "frame_end", text="Total Frames")
row.prop(scene.render, "fps", text="FPS")
# ── Random Generator ──────────────────────────────────────────────────
gen_box = layout.box()
gen_hdr = gen_box.row()
gen_hdr.prop(gen, "ui_show_generator",
icon='TRIA_DOWN' if gen.ui_show_generator else 'TRIA_RIGHT',
icon_only=True, emboss=False)
gen_hdr.label(text="Random Generator", icon='GROUP')
if gen.ui_show_generator:
gen_box.prop(gen, "num_knots")
# · Base Knot Defaults ·
base_box = gen_box.box()
base_row = base_box.row()
base_row.prop(gen, "ui_show_base",
icon='TRIA_DOWN' if gen.ui_show_base else 'TRIA_RIGHT',
icon_only=True, emboss=False)
base_row.label(text="Base Defaults", icon='PRESET')
if gen.ui_show_base:
draw_knot_properties(base_box, gen.base_knot)
# · Randomise Toggles (collapsible container) ·
rand_box = gen_box.box()
rand_hdr = rand_box.row()
rand_hdr.prop(gen, "ui_show_rand_toggles",
icon='TRIA_DOWN' if gen.ui_show_rand_toggles else 'TRIA_RIGHT',
icon_only=True, emboss=False)
rand_hdr.label(text="Randomise Toggles", icon='MODIFIER')
if gen.ui_show_rand_toggles:
def draw_rand(prop):
row = rand_box.row(align=True)
row.prop(gen, f"r_{prop}")
if getattr(gen, f"r_{prop}"):
row.prop(gen, f"min_{prop}")
row.prop(gen, f"max_{prop}")
def draw_rand_bool(prop):
row = rand_box.row(align=True)
row.prop(gen, f"r_{prop}")
if getattr(gen, f"r_{prop}") and hasattr(gen, f"prob_{prop}"):
row.prop(gen, f"prob_{prop}")
# ·· Shape ··
s_box = rand_box.box()
s_row = s_box.row()
s_row.prop(gen, "ui_show_rand_shape",
icon='TRIA_DOWN' if gen.ui_show_rand_shape else 'TRIA_RIGHT',
icon_only=True, emboss=False)
s_row.label(text="Shape", icon='CURVE_PATH')
if gen.ui_show_rand_shape:
s_box.prop(gen, "r_shape_type")
draw_rand("torus_p"); draw_rand("torus_q")
draw_rand_bool("flip_p"); draw_rand_bool("flip_q")
draw_rand_bool("mode")
draw_rand("torus_R"); draw_rand("torus_r")
draw_rand("torus_eR"); draw_rand("torus_iR")
draw_rand("mobius_twists"); draw_rand("mobius_width")
draw_rand("liss_kx"); draw_rand("liss_ky")
draw_rand("liss_kz"); draw_rand("liss_amp")
draw_rand("spiral_turns"); draw_rand("spiral_R")
# ·· Geometry & Links ··
g_box = rand_box.box()
g_row = g_box.row()
g_row.prop(gen, "ui_show_rand_geo",
icon='TRIA_DOWN' if gen.ui_show_rand_geo else 'TRIA_RIGHT',
icon_only=True, emboss=False)
g_row.label(text="Geometry & Links", icon='MESH_DATA')
if gen.ui_show_rand_geo:
draw_rand("geo_extrude"); draw_rand("geo_offset")
draw_rand("geo_bDepth"); draw_rand("torus_h")
draw_rand_bool("multiple_links")
draw_rand("torus_u"); draw_rand("torus_v")
draw_rand("torus_rP"); draw_rand("torus_sP")
draw_rand("transition_frames")
draw_rand_bool("transition_easing")
# ·· Animation Rates ··
a_box = rand_box.box()
a_row = a_box.row()
a_row.prop(gen, "ui_show_rand_anim",
icon='TRIA_DOWN' if gen.ui_show_rand_anim else 'TRIA_RIGHT',
icon_only=True, emboss=False)
a_row.label(text="Animation Rates", icon='ANIM')
if gen.ui_show_rand_anim:
draw_rand("cycle_rate")
draw_rand("spin_phase_rate"); draw_rand("rev_phase_rate")
draw_rand("height_rate")
draw_rand("scale_rate"); draw_rand("scale_amplitude")
# ·· Material ··
m_box = rand_box.box()
m_row = m_box.row()
m_row.prop(gen, "ui_show_rand_mat",
icon='TRIA_DOWN' if gen.ui_show_rand_mat else 'TRIA_RIGHT',
icon_only=True, emboss=False)
m_row.label(text="Material", icon='MATERIAL')
if gen.ui_show_rand_mat:
row = m_box.row(align=True)
row.prop(gen, "r_material")
if gen.r_material:
row.prop(gen, "r_preset_params", text="Rnd Params")
filter_box = m_box.box()
filter_box.label(text="Allowed Materials & Presets:")
row_sync = filter_box.row()
row_sync.template_list(
"KNOT_UL_AllowedMaterialsList", "",
gen, "allowed_materials",
gen, "allowed_materials_index",
rows=5)
sync_col = row_sync.column(align=True)
sync_col.operator(
"knot.sync_generator_materials",
icon='FILE_REFRESH', text=""
)
if len(gen.allowed_materials) == 0:
filter_box.label(
text="Click Sync to load project materials & presets",
icon='INFO')
gen_box.operator("knot.generate_random", icon='PLAY')
# ── Export ────────────────────────────────────────────────────────────
ex_box = layout.box()
ex_box.label(text="Export", icon='EXPORT')
ex_box.label(text="Bake all frames to a standalone .blend file",
icon='INFO')
row = ex_box.row()
row.scale_y = 1.4
row.operator("knot.bake_export", icon='RENDER_ANIMATION')