Big update
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
knot_animation/__pycache__
|
||||||
@@ -45,6 +45,8 @@ def _make_torus_knot(
|
|||||||
bevel_resolution: int,
|
bevel_resolution: int,
|
||||||
knot_scale: float,
|
knot_scale: float,
|
||||||
scene=None,
|
scene=None,
|
||||||
|
turbulence: float = 0.0,
|
||||||
|
smooth_shading: bool = True,
|
||||||
) -> bpy.types.Object:
|
) -> bpy.types.Object:
|
||||||
"""Procedurally build / update the AnimKnot NURBS curve in-place.
|
"""Procedurally build / update the AnimKnot NURBS curve in-place.
|
||||||
|
|
||||||
@@ -99,6 +101,9 @@ def _make_torus_knot(
|
|||||||
# 3. Resolve shape parameters
|
# 3. Resolve shape parameters
|
||||||
shape_type = config.get('shape_type', 'TORUS_KNOT')
|
shape_type = config.get('shape_type', 'TORUS_KNOT')
|
||||||
|
|
||||||
|
rP = config.get('torus_rP', 0.0) * math.pi * 2.0
|
||||||
|
sP = config.get('torus_sP', 0.0) * math.pi * 2.0
|
||||||
|
|
||||||
if shape_type == 'TORUS_KNOT':
|
if shape_type == 'TORUS_KNOT':
|
||||||
p = config.get('torus_p', 2)
|
p = config.get('torus_p', 2)
|
||||||
q = config.get('torus_q', 3)
|
q = config.get('torus_q', 3)
|
||||||
@@ -117,8 +122,6 @@ def _make_torus_knot(
|
|||||||
|
|
||||||
u = config.get('torus_u', 1)
|
u = config.get('torus_u', 1)
|
||||||
v = config.get('torus_v', 1)
|
v = config.get('torus_v', 1)
|
||||||
rP = config.get('torus_rP', 0.0) * math.pi * 2.0
|
|
||||||
sP = config.get('torus_sP', 0.0) * math.pi * 2.0
|
|
||||||
h = config.get('torus_h', 1.0)
|
h = config.get('torus_h', 1.0)
|
||||||
|
|
||||||
multiple_links = config.get('multiple_links', False)
|
multiple_links = config.get('multiple_links', False)
|
||||||
@@ -197,20 +200,23 @@ def _make_torus_knot(
|
|||||||
t_param = (i / steps) * t_max
|
t_param = (i / steps) * t_max
|
||||||
w_eff = w if link == 0 else -w
|
w_eff = w if link == 0 else -w
|
||||||
|
|
||||||
x = (R + w_eff * math.cos(twists * t_param / 2.0)) * math.cos(t_param)
|
t_param_rot = t_param + rP
|
||||||
y = (R + w_eff * math.cos(twists * t_param / 2.0)) * math.sin(t_param)
|
twist_angle = twists * t_param_rot / 2.0 + sP
|
||||||
z = w_eff * math.sin(twists * t_param / 2.0)
|
|
||||||
|
x = (R + w_eff * math.cos(twist_angle)) * math.cos(t_param_rot)
|
||||||
|
y = (R + w_eff * math.cos(twist_angle)) * math.sin(t_param_rot)
|
||||||
|
z = w_eff * math.sin(twist_angle)
|
||||||
|
|
||||||
elif shape_type == 'LISSAJOUS':
|
elif shape_type == 'LISSAJOUS':
|
||||||
t_param = (i / steps) * TAU
|
t_param = (i / steps) * TAU
|
||||||
x = amp * math.sin(kx * t_param)
|
x = amp * math.sin(kx * t_param + rP)
|
||||||
y = amp * math.sin(ky * t_param + (TAU / 4.0))
|
y = amp * math.sin(ky * t_param + (TAU / 4.0) + sP)
|
||||||
z = amp * math.sin(kz * t_param)
|
z = amp * math.sin(kz * t_param + rP + sP)
|
||||||
|
|
||||||
elif shape_type == 'SPIRAL':
|
elif shape_type == 'SPIRAL':
|
||||||
t_param = -math.pi / 2.0 + (i / max(1, steps - 1)) * math.pi
|
t_param = -math.pi / 2.0 + (i / max(1, steps - 1)) * math.pi
|
||||||
theta = t_param
|
theta = t_param
|
||||||
phi = turns * 2.0 * t_param
|
phi = turns * 2.0 * t_param + rP + sP
|
||||||
x = R * math.cos(theta) * math.cos(phi)
|
x = R * math.cos(theta) * math.cos(phi)
|
||||||
y = R * math.cos(theta) * math.sin(phi)
|
y = R * math.cos(theta) * math.sin(phi)
|
||||||
z = R * math.sin(theta)
|
z = R * math.sin(theta)
|
||||||
@@ -218,6 +224,14 @@ def _make_torus_knot(
|
|||||||
else:
|
else:
|
||||||
x, y, z = 0.0, 0.0, 0.0
|
x, y, z = 0.0, 0.0, 0.0
|
||||||
|
|
||||||
|
if turbulence > 0.0:
|
||||||
|
nx = math.sin(i * 1.345 + rP) * turbulence
|
||||||
|
ny = math.cos(i * 0.932 - sP) * turbulence
|
||||||
|
nz = math.sin(i * 1.777 + rP + sP) * turbulence
|
||||||
|
x += nx
|
||||||
|
y += ny
|
||||||
|
z += nz
|
||||||
|
|
||||||
spline.points[i].co = (x, y, z, 1.0)
|
spline.points[i].co = (x, y, z, 1.0)
|
||||||
|
|
||||||
# 6. Transform and material
|
# 6. Transform and material
|
||||||
|
|||||||
+100
-21
@@ -139,33 +139,33 @@ def knot_frame_handler(scene, depsgraph=None) -> None:
|
|||||||
config[key] = val_b
|
config[key] = val_b
|
||||||
|
|
||||||
# Animated spin phase
|
# Animated spin phase
|
||||||
sP_a = item.torus_sP + effective_f * item.spin_phase_rate * reactivity
|
sP_a = item.torus_sP + effective_f * item.spin_phase_rate
|
||||||
sP_b = next_item.torus_sP + effective_f * next_item.spin_phase_rate * reactivity
|
sP_b = next_item.torus_sP + effective_f * next_item.spin_phase_rate
|
||||||
config["torus_sP"] = sP_a + (sP_b - sP_a) * t
|
config["torus_sP"] = sP_a + (sP_b - sP_a) * t
|
||||||
|
|
||||||
# Animated revolution phase
|
# Animated revolution phase
|
||||||
rP_a = item.torus_rP + effective_f * item.rev_phase_rate * reactivity
|
rP_a = item.torus_rP + effective_f * item.rev_phase_rate
|
||||||
rP_b = next_item.torus_rP + effective_f * next_item.rev_phase_rate * reactivity
|
rP_b = next_item.torus_rP + effective_f * next_item.rev_phase_rate
|
||||||
config["torus_rP"] = rP_a + (rP_b - rP_a) * t
|
config["torus_rP"] = rP_a + (rP_b - rP_a) * t
|
||||||
|
|
||||||
# Animated height pulse
|
# Animated height pulse
|
||||||
h_a = item.torus_h + (
|
h_a = item.torus_h + (
|
||||||
math.sin(effective_f * item.height_rate * reactivity) * abs(item.height_rate) * 5.0
|
math.sin(effective_f * item.height_rate) * abs(item.height_rate) * 5.0 * reactivity
|
||||||
if item.height_rate != 0.0 else 0.0
|
if item.height_rate != 0.0 else 0.0
|
||||||
)
|
)
|
||||||
h_b = next_item.torus_h + (
|
h_b = next_item.torus_h + (
|
||||||
math.sin(effective_f * next_item.height_rate * reactivity) * abs(next_item.height_rate) * 5.0
|
math.sin(effective_f * next_item.height_rate) * abs(next_item.height_rate) * 5.0 * reactivity
|
||||||
if next_item.height_rate != 0.0 else 0.0
|
if next_item.height_rate != 0.0 else 0.0
|
||||||
)
|
)
|
||||||
config["torus_h"] = h_a + (h_b - h_a) * t
|
config["torus_h"] = h_a + (h_b - h_a) * t
|
||||||
|
|
||||||
# Animated scale oscillation
|
# Animated scale oscillation
|
||||||
sc_a = (
|
sc_a = (
|
||||||
1.0 + item.scale_amplitude * math.sin(effective_f * item.scale_rate * reactivity)
|
1.0 + item.scale_amplitude * math.sin(effective_f * item.scale_rate) * reactivity
|
||||||
if item.scale_amplitude > 0.0 and item.scale_rate != 0.0 else None
|
if item.scale_amplitude > 0.0 and item.scale_rate != 0.0 else None
|
||||||
)
|
)
|
||||||
sc_b = (
|
sc_b = (
|
||||||
1.0 + next_item.scale_amplitude * math.sin(effective_f * next_item.scale_rate * reactivity)
|
1.0 + next_item.scale_amplitude * math.sin(effective_f * next_item.scale_rate) * reactivity
|
||||||
if next_item.scale_amplitude > 0.0 and next_item.scale_rate != 0.0 else None
|
if next_item.scale_amplitude > 0.0 and next_item.scale_rate != 0.0 else None
|
||||||
)
|
)
|
||||||
if sc_a is not None or sc_b is not None:
|
if sc_a is not None or sc_b is not None:
|
||||||
@@ -176,21 +176,39 @@ def knot_frame_handler(scene, depsgraph=None) -> None:
|
|||||||
else:
|
else:
|
||||||
# Apply animated rates with reactivity (no transition)
|
# Apply animated rates with reactivity (no transition)
|
||||||
if item.spin_phase_rate != 0.0:
|
if item.spin_phase_rate != 0.0:
|
||||||
config["torus_sP"] += effective_f * item.spin_phase_rate * reactivity
|
config["torus_sP"] += effective_f * item.spin_phase_rate
|
||||||
|
|
||||||
if item.rev_phase_rate != 0.0:
|
if item.rev_phase_rate != 0.0:
|
||||||
config["torus_rP"] += effective_f * item.rev_phase_rate * reactivity
|
config["torus_rP"] += effective_f * item.rev_phase_rate
|
||||||
|
|
||||||
if item.height_rate != 0.0:
|
if item.height_rate != 0.0:
|
||||||
config["torus_h"] += (
|
config["torus_h"] += (
|
||||||
math.sin(effective_f * item.height_rate * reactivity) * abs(item.height_rate) * 5.0
|
math.sin(effective_f * item.height_rate) * abs(item.height_rate) * 5.0 * reactivity
|
||||||
)
|
)
|
||||||
|
|
||||||
if item.scale_amplitude > 0.0 and item.scale_rate != 0.0:
|
if item.scale_amplitude > 0.0 and item.scale_rate != 0.0:
|
||||||
config["_scale_override"] = (
|
config["_scale_override"] = (
|
||||||
1.0 + item.scale_amplitude * math.sin(effective_f * item.scale_rate * reactivity)
|
1.0 + item.scale_amplitude * math.sin(effective_f * item.scale_rate) * reactivity
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ── Apply Attenuverter Modulations ───────────────────────────────────────
|
||||||
|
# Float mods
|
||||||
|
for key in [
|
||||||
|
"torus_R", "torus_r", "torus_eR", "torus_iR", "torus_h",
|
||||||
|
"mobius_width", "liss_amp", "spiral_R",
|
||||||
|
"geo_extrude", "geo_offset", "geo_bDepth"
|
||||||
|
]:
|
||||||
|
if f"mod_{key}" in config and config[f"mod_{key}"] != 0.0:
|
||||||
|
config[key] += config[f"mod_{key}"] * reactivity
|
||||||
|
|
||||||
|
# Integer mods (clamp to minimum 1 for topology safety)
|
||||||
|
for key in [
|
||||||
|
"torus_p", "torus_q", "mobius_twists",
|
||||||
|
"liss_kx", "liss_ky", "liss_kz", "spiral_turns"
|
||||||
|
]:
|
||||||
|
if f"mod_{key}" in config and config[f"mod_{key}"] != 0.0:
|
||||||
|
config[key] = max(1, config[key] + int(config[f"mod_{key}"] * reactivity))
|
||||||
|
|
||||||
# ── Cross-material blend ─────────────────────────────────────────────────
|
# ── Cross-material blend ─────────────────────────────────────────────────
|
||||||
cross_material = False
|
cross_material = False
|
||||||
if transition_active:
|
if transition_active:
|
||||||
@@ -200,26 +218,87 @@ def knot_frame_handler(scene, depsgraph=None) -> None:
|
|||||||
if cross_material:
|
if cross_material:
|
||||||
config["_skip_material"] = True
|
config["_skip_material"] = True
|
||||||
|
|
||||||
|
# ── Apply Global Settings & Overrides ────────────────────────────────────
|
||||||
|
is_render = False
|
||||||
|
if depsgraph is not None and getattr(depsgraph, "mode", 'VIEWPORT') == 'RENDER':
|
||||||
|
is_render = True
|
||||||
|
|
||||||
|
res = glob.render_resolution if is_render else glob.preview_resolution
|
||||||
|
bevel_res = glob.render_bevel_resolution if is_render else glob.preview_bevel_resolution
|
||||||
|
|
||||||
|
if "geo_bDepth" in config:
|
||||||
|
config["geo_bDepth"] *= glob.global_master_thickness
|
||||||
|
|
||||||
|
if "preset_emission_strength" in config:
|
||||||
|
config["preset_emission_strength"] *= glob.global_emission_multiplier
|
||||||
|
|
||||||
|
if "preset_color" in config and glob.global_hue_shift != 0.0:
|
||||||
|
import colorsys
|
||||||
|
color_val = config["preset_color"]
|
||||||
|
if len(color_val) == 4:
|
||||||
|
r, g, b, a = color_val
|
||||||
|
else:
|
||||||
|
r, g, b = color_val
|
||||||
|
a = 1.0
|
||||||
|
|
||||||
|
h_hsv, s, v = colorsys.rgb_to_hsv(r, g, b)
|
||||||
|
h_hsv = (h_hsv + glob.global_hue_shift) % 1.0
|
||||||
|
nr, ng, nb = colorsys.hsv_to_rgb(h_hsv, s, v)
|
||||||
|
|
||||||
|
if len(color_val) == 4:
|
||||||
|
config["preset_color"] = (nr, ng, nb, a)
|
||||||
|
else:
|
||||||
|
config["preset_color"] = (nr, ng, nb)
|
||||||
|
|
||||||
# Resolve globals once and pass explicitly — no bpy.context inside geometry
|
# Resolve globals once and pass explicitly — no bpy.context inside geometry
|
||||||
_make_torus_knot(
|
_make_torus_knot(
|
||||||
config,
|
config,
|
||||||
resolution=glob.resolution,
|
resolution=res,
|
||||||
bevel_resolution=glob.bevel_resolution,
|
bevel_resolution=bevel_res,
|
||||||
knot_scale=glob.knot_scale,
|
knot_scale=glob.knot_scale,
|
||||||
scene=scene,
|
scene=scene,
|
||||||
|
turbulence=glob.global_turbulence,
|
||||||
|
smooth_shading=glob.smooth_shading,
|
||||||
)
|
)
|
||||||
|
|
||||||
if cross_material:
|
if cross_material:
|
||||||
blend_name = f"KnotBlend_Item_{idx}"
|
blend_name = f"KnotBlend_Item_{idx}"
|
||||||
blend_mat = bpy.data.materials.get(blend_name)
|
blend_mat = bpy.data.materials.get(blend_name)
|
||||||
if blend_mat:
|
blend_obj = bpy.data.objects.get(KNOT_OBJ_NAME)
|
||||||
mix_node = blend_mat.node_tree.nodes.get("_KnotBlendMix")
|
if blend_obj:
|
||||||
if mix_node:
|
if blend_mat:
|
||||||
mix_node.inputs["Fac"].default_value = float(t)
|
mix_node = blend_mat.node_tree.nodes.get("_KnotBlendMix")
|
||||||
|
if mix_node:
|
||||||
blend_obj = bpy.data.objects.get(KNOT_OBJ_NAME)
|
mix_node.inputs["Fac"].default_value = float(t)
|
||||||
if blend_obj:
|
|
||||||
if len(blend_obj.data.materials) == 0:
|
if len(blend_obj.data.materials) == 0:
|
||||||
blend_obj.data.materials.append(blend_mat)
|
blend_obj.data.materials.append(blend_mat)
|
||||||
else:
|
else:
|
||||||
blend_obj.data.materials[0] = blend_mat
|
blend_obj.data.materials[0] = blend_mat
|
||||||
|
elif mat_a:
|
||||||
|
# Fallback if blend material wasn't generated
|
||||||
|
if len(blend_obj.data.materials) == 0:
|
||||||
|
blend_obj.data.materials.append(mat_a)
|
||||||
|
else:
|
||||||
|
blend_obj.data.materials[0] = mat_a
|
||||||
|
|
||||||
|
# ── Post-Geometry Object Overrides ───────────────────────────────────────
|
||||||
|
knot_obj = bpy.data.objects.get(KNOT_OBJ_NAME)
|
||||||
|
if knot_obj:
|
||||||
|
knot_obj.display_type = 'WIRE' if glob.viewport_wireframe else 'TEXTURED'
|
||||||
|
knot_obj.rotation_euler[2] = effective_f * glob.auto_turntable_speed
|
||||||
|
|
||||||
|
# ── Camera Shake ─────────────────────────────────────────────────────────
|
||||||
|
cam = scene.camera
|
||||||
|
if cam:
|
||||||
|
shake = glob.camera_shake_amplitude
|
||||||
|
if shake > 0.0:
|
||||||
|
import random
|
||||||
|
# Deterministic noise based on frame for stable rendering
|
||||||
|
random.seed(int(f * 1000))
|
||||||
|
cam.delta_location = (
|
||||||
|
random.uniform(-shake, shake),
|
||||||
|
random.uniform(-shake, shake),
|
||||||
|
random.uniform(-shake, shake)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cam.delta_location = (0.0, 0.0, 0.0)
|
||||||
|
|||||||
@@ -250,6 +250,20 @@ class KNOT_OT_GenerateRandom(bpy.types.Operator):
|
|||||||
_rand_apply_float(gen, item, "scale_amplitude")
|
_rand_apply_float(gen, item, "scale_amplitude")
|
||||||
_rand_apply_float(gen, item, "cycle_rate")
|
_rand_apply_float(gen, item, "cycle_rate")
|
||||||
|
|
||||||
|
if getattr(gen, "r_modulations", False):
|
||||||
|
import random
|
||||||
|
for k in [
|
||||||
|
"mod_torus_R", "mod_torus_r", "mod_torus_eR", "mod_torus_iR", "mod_torus_h",
|
||||||
|
"mod_mobius_width", "mod_liss_amp", "mod_spiral_R",
|
||||||
|
"mod_geo_extrude", "mod_geo_offset", "mod_geo_bDepth"
|
||||||
|
]:
|
||||||
|
setattr(item, k, random.uniform(-0.5, 0.5))
|
||||||
|
for k in [
|
||||||
|
"mod_torus_p", "mod_torus_q", "mod_mobius_twists",
|
||||||
|
"mod_liss_kx", "mod_liss_ky", "mod_liss_kz", "mod_spiral_turns"
|
||||||
|
]:
|
||||||
|
setattr(item, k, random.uniform(-2.0, 2.0))
|
||||||
|
|
||||||
_rand_apply_bool(gen, item, "multiple_links")
|
_rand_apply_bool(gen, item, "multiple_links")
|
||||||
_rand_apply_bool(gen, item, "use_colors")
|
_rand_apply_bool(gen, item, "use_colors")
|
||||||
_rand_apply_bool(gen, item, "random_colors")
|
_rand_apply_bool(gen, item, "random_colors")
|
||||||
|
|||||||
+112
-15
@@ -79,19 +79,30 @@ def _update_knot_material_cb(self, context) -> None:
|
|||||||
if not self.uid:
|
if not self.uid:
|
||||||
self.uid = f"knot_{random.randint(100000, 999999)}"
|
self.uid = f"knot_{random.randint(100000, 999999)}"
|
||||||
|
|
||||||
if self.material_mode == 'PRESET':
|
try:
|
||||||
_ensure_item_preset_material(self)
|
scene = getattr(context, "scene", None)
|
||||||
|
if scene is None:
|
||||||
|
return
|
||||||
|
|
||||||
# Rebuild blend materials to keep transitions up to date
|
if self.material_mode == 'PRESET':
|
||||||
prebuild_playlist_blend_materials(context.scene)
|
_ensure_item_preset_material(self)
|
||||||
|
|
||||||
# Force redraw of 3-D viewport
|
# Rebuild blend materials to keep transitions up to date
|
||||||
for area in context.screen.areas:
|
prebuild_playlist_blend_materials(scene)
|
||||||
if area.type == 'VIEW_3D':
|
|
||||||
area.tag_redraw()
|
|
||||||
|
|
||||||
# Instantly update the active viewport knot's material
|
# Instantly update the active viewport knot's material
|
||||||
_update_viewport_knot_material(context.scene)
|
_update_viewport_knot_material(scene)
|
||||||
|
|
||||||
|
# Force redraw of 3-D viewport if screen exists
|
||||||
|
screen = getattr(context, "screen", None)
|
||||||
|
if screen:
|
||||||
|
for area in screen.areas:
|
||||||
|
if area.type == 'VIEW_3D':
|
||||||
|
area.tag_redraw()
|
||||||
|
except Exception:
|
||||||
|
# Safe fallback: if this callback fires during depsgraph evaluation
|
||||||
|
# (e.g., driven by an F-curve), context accesses will throw internal state bugs.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -111,10 +122,14 @@ class KnotGlobalSettings(bpy.types.PropertyGroup):
|
|||||||
"""Scene-level playback and rendering settings."""
|
"""Scene-level playback and rendering settings."""
|
||||||
frames_per_knot: bpy.props.IntProperty(
|
frames_per_knot: bpy.props.IntProperty(
|
||||||
name="Frames per Knot", default=12, min=1)
|
name="Frames per Knot", default=12, min=1)
|
||||||
resolution: bpy.props.IntProperty(
|
preview_resolution: bpy.props.IntProperty(
|
||||||
name="Curve Resolution", default=128, min=3, max=1024)
|
name="Preview Curve Res", default=64, min=3, max=1024)
|
||||||
bevel_resolution: bpy.props.IntProperty(
|
render_resolution: bpy.props.IntProperty(
|
||||||
name="Bevel Resolution", default=8, min=0, max=64)
|
name="Render Curve Res", default=128, min=3, max=2048)
|
||||||
|
preview_bevel_resolution: bpy.props.IntProperty(
|
||||||
|
name="Preview Bevel Res", default=4, min=0, max=64)
|
||||||
|
render_bevel_resolution: bpy.props.IntProperty(
|
||||||
|
name="Render Bevel Res", default=8, min=0, max=64)
|
||||||
knot_scale: bpy.props.FloatProperty(
|
knot_scale: bpy.props.FloatProperty(
|
||||||
name="Global Scale", default=1.0, min=0.01)
|
name="Global Scale", default=1.0, min=0.01)
|
||||||
global_speed: bpy.props.FloatProperty(
|
global_speed: bpy.props.FloatProperty(
|
||||||
@@ -127,6 +142,38 @@ class KnotGlobalSettings(bpy.types.PropertyGroup):
|
|||||||
name="Reactivity", default=1.0, min=0.0, max=10.0,
|
name="Reactivity", default=1.0, min=0.0, max=10.0,
|
||||||
description="Scales all per-knot animation rates. Wire a driver here for audio-reactive animation.")
|
description="Scales all per-knot animation rates. Wire a driver here for audio-reactive animation.")
|
||||||
|
|
||||||
|
# New 8 Global Variables
|
||||||
|
global_turbulence: bpy.props.FloatProperty(
|
||||||
|
name="Turbulence (Noise)", default=0.0, min=0.0,
|
||||||
|
description="Injects 3D noise/displacement into the final curve vertices.")
|
||||||
|
global_emission_multiplier: bpy.props.FloatProperty(
|
||||||
|
name="Emission Multiplier", default=1.0, min=0.0,
|
||||||
|
description="Master scalar for the shader's emission strength.")
|
||||||
|
global_master_thickness: bpy.props.FloatProperty(
|
||||||
|
name="Master Thickness", default=1.0, min=0.0,
|
||||||
|
description="Multiplier for the knot's bevel depth.")
|
||||||
|
global_hue_shift: bpy.props.FloatProperty(
|
||||||
|
name="Hue Shift", default=0.0,
|
||||||
|
description="Offset applied to the material's color hue.")
|
||||||
|
camera_shake_amplitude: bpy.props.FloatProperty(
|
||||||
|
name="Camera Shake Amp", default=0.0, min=0.0,
|
||||||
|
description="Injects X/Y/Z jitter into the active camera's location.")
|
||||||
|
auto_turntable_speed: bpy.props.FloatProperty(
|
||||||
|
name="Turntable Speed", default=0.0,
|
||||||
|
description="Constant Z-axis rotation speed applied to the master knot object.")
|
||||||
|
smooth_shading: bpy.props.BoolProperty(
|
||||||
|
name="Smooth Shading", default=True,
|
||||||
|
description="Enforce Smooth shading on the generated mesh.")
|
||||||
|
viewport_wireframe: bpy.props.BoolProperty(
|
||||||
|
name="Viewport Wireframe", default=False,
|
||||||
|
description="Force the object to display as wireframe in the 3D viewport.")
|
||||||
|
|
||||||
|
# UI expand state
|
||||||
|
ui_show_global: bpy.props.BoolProperty(name="Global Settings", default=True)
|
||||||
|
ui_show_playback: bpy.props.BoolProperty(name="Playback", default=True)
|
||||||
|
ui_show_playlist: bpy.props.BoolProperty(name="Playlist", default=True)
|
||||||
|
ui_show_knot_edit: bpy.props.BoolProperty(name="Selected Knot", default=True)
|
||||||
|
|
||||||
|
|
||||||
class KnotItem(bpy.types.PropertyGroup):
|
class KnotItem(bpy.types.PropertyGroup):
|
||||||
"""One entry in the knot playlist."""
|
"""One entry in the knot playlist."""
|
||||||
@@ -147,25 +194,37 @@ class KnotItem(bpy.types.PropertyGroup):
|
|||||||
|
|
||||||
# Topology (Torus Knot)
|
# Topology (Torus Knot)
|
||||||
torus_p: bpy.props.IntProperty(name="Revolutions (p)", default=2, min=1)
|
torus_p: bpy.props.IntProperty(name="Revolutions (p)", default=2, min=1)
|
||||||
|
mod_torus_p: bpy.props.FloatProperty(name="Mod p", default=0.0)
|
||||||
torus_q: bpy.props.IntProperty(name="Spins (q)", default=3, min=1)
|
torus_q: bpy.props.IntProperty(name="Spins (q)", default=3, min=1)
|
||||||
|
mod_torus_q: bpy.props.FloatProperty(name="Mod q", default=0.0)
|
||||||
|
|
||||||
# Topology (Mobius)
|
# Topology (Mobius)
|
||||||
mobius_twists: bpy.props.IntProperty(name="Half Twists", default=1, min=1)
|
mobius_twists: bpy.props.IntProperty(name="Half Twists", default=1, min=1)
|
||||||
|
mod_mobius_twists: bpy.props.FloatProperty(name="Mod Twists", default=0.0)
|
||||||
mobius_width: bpy.props.FloatProperty(name="Width", default=1.0, min=0.1)
|
mobius_width: bpy.props.FloatProperty(name="Width", default=1.0, min=0.1)
|
||||||
|
mod_mobius_width: bpy.props.FloatProperty(name="Mod Width", default=0.0)
|
||||||
|
|
||||||
# Topology (Lissajous 3D)
|
# Topology (Lissajous 3D)
|
||||||
liss_kx: bpy.props.IntProperty(name="kx (Freq X)", default=3, min=1)
|
liss_kx: bpy.props.IntProperty(name="kx (Freq X)", default=3, min=1)
|
||||||
|
mod_liss_kx: bpy.props.FloatProperty(name="Mod kx", default=0.0)
|
||||||
liss_ky: bpy.props.IntProperty(name="ky (Freq Y)", default=2, min=1)
|
liss_ky: bpy.props.IntProperty(name="ky (Freq Y)", default=2, min=1)
|
||||||
|
mod_liss_ky: bpy.props.FloatProperty(name="Mod ky", default=0.0)
|
||||||
liss_kz: bpy.props.IntProperty(name="kz (Freq Z)", default=4, min=1)
|
liss_kz: bpy.props.IntProperty(name="kz (Freq Z)", default=4, min=1)
|
||||||
|
mod_liss_kz: bpy.props.FloatProperty(name="Mod kz", default=0.0)
|
||||||
liss_amp: bpy.props.FloatProperty(name="Amplitude", default=2.0, min=0.1)
|
liss_amp: bpy.props.FloatProperty(name="Amplitude", default=2.0, min=0.1)
|
||||||
|
mod_liss_amp: bpy.props.FloatProperty(name="Mod Amp", default=0.0)
|
||||||
|
|
||||||
# Topology (Spherical Spiral)
|
# Topology (Spherical Spiral)
|
||||||
spiral_turns: bpy.props.IntProperty(name="Turns", default=10, min=1)
|
spiral_turns: bpy.props.IntProperty(name="Turns", default=10, min=1)
|
||||||
|
mod_spiral_turns: bpy.props.FloatProperty(name="Mod Turns", default=0.0)
|
||||||
spiral_R: bpy.props.FloatProperty(name="Radius", default=2.0, min=0.1)
|
spiral_R: bpy.props.FloatProperty(name="Radius", default=2.0, min=0.1)
|
||||||
|
mod_spiral_R: bpy.props.FloatProperty(name="Mod Radius", default=0.0)
|
||||||
|
|
||||||
# Radii (Torus Knot)
|
# Radii (Torus Knot)
|
||||||
torus_R: bpy.props.FloatProperty(name="Major", default=2.0, min=0.0)
|
torus_R: bpy.props.FloatProperty(name="Major", default=2.0, min=0.0)
|
||||||
|
mod_torus_R: bpy.props.FloatProperty(name="Mod Major", default=0.0)
|
||||||
torus_r: bpy.props.FloatProperty(name="Minor", default=1.0, min=0.0)
|
torus_r: bpy.props.FloatProperty(name="Minor", default=1.0, min=0.0)
|
||||||
|
mod_torus_r: bpy.props.FloatProperty(name="Mod Minor", default=0.0)
|
||||||
|
|
||||||
# Colors (legacy TKP path)
|
# Colors (legacy TKP path)
|
||||||
multiple_links: bpy.props.BoolProperty(name="Multiple Links", default=False)
|
multiple_links: bpy.props.BoolProperty(name="Multiple Links", default=False)
|
||||||
@@ -205,7 +264,8 @@ class KnotItem(bpy.types.PropertyGroup):
|
|||||||
# Transition
|
# Transition
|
||||||
transition_frames: bpy.props.IntProperty(
|
transition_frames: bpy.props.IntProperty(
|
||||||
name="Transition Frames", default=0, min=0,
|
name="Transition Frames", default=0, min=0,
|
||||||
description="Number of frames to morph into the next knot (0 = instant)")
|
description="Number of frames to morph into the next knot (0 = instant)",
|
||||||
|
update=_update_knot_material_cb)
|
||||||
transition_easing: bpy.props.EnumProperty(
|
transition_easing: bpy.props.EnumProperty(
|
||||||
name="Easing",
|
name="Easing",
|
||||||
items=[
|
items=[
|
||||||
@@ -217,8 +277,11 @@ class KnotItem(bpy.types.PropertyGroup):
|
|||||||
|
|
||||||
# Geometry
|
# Geometry
|
||||||
geo_extrude: bpy.props.FloatProperty(name="Extrude", default=0.0, min=0.0)
|
geo_extrude: bpy.props.FloatProperty(name="Extrude", default=0.0, min=0.0)
|
||||||
|
mod_geo_extrude: bpy.props.FloatProperty(name="Mod Extrude", default=0.0)
|
||||||
geo_offset: bpy.props.FloatProperty(name="Offset", default=0.0)
|
geo_offset: bpy.props.FloatProperty(name="Offset", default=0.0)
|
||||||
|
mod_geo_offset: bpy.props.FloatProperty(name="Mod Offset", default=0.0)
|
||||||
geo_bDepth: bpy.props.FloatProperty(name="Bevel Depth", default=0.04, min=0.0)
|
geo_bDepth: bpy.props.FloatProperty(name="Bevel Depth", default=0.04, min=0.0)
|
||||||
|
mod_geo_bDepth: bpy.props.FloatProperty(name="Mod BDepth", default=0.0)
|
||||||
|
|
||||||
# Dimensions mode
|
# Dimensions mode
|
||||||
mode: bpy.props.EnumProperty(
|
mode: bpy.props.EnumProperty(
|
||||||
@@ -226,13 +289,25 @@ class KnotItem(bpy.types.PropertyGroup):
|
|||||||
items=[('MAJOR_MINOR', "Major/Minor", ""), ('EXT_INT', "Exterior/Interior", "")],
|
items=[('MAJOR_MINOR', "Major/Minor", ""), ('EXT_INT', "Exterior/Interior", "")],
|
||||||
default='MAJOR_MINOR')
|
default='MAJOR_MINOR')
|
||||||
torus_eR: bpy.props.FloatProperty(name="Exterior", default=3.0, min=0.0)
|
torus_eR: bpy.props.FloatProperty(name="Exterior", default=3.0, min=0.0)
|
||||||
|
mod_torus_eR: bpy.props.FloatProperty(name="Mod Ext", default=0.0)
|
||||||
torus_iR: bpy.props.FloatProperty(name="Interior", default=1.0, min=0.0)
|
torus_iR: bpy.props.FloatProperty(name="Interior", default=1.0, min=0.0)
|
||||||
|
mod_torus_iR: bpy.props.FloatProperty(name="Mod Int", default=0.0)
|
||||||
torus_h: bpy.props.FloatProperty(name="Height", default=1.0, min=0.0)
|
torus_h: bpy.props.FloatProperty(name="Height", default=1.0, min=0.0)
|
||||||
|
mod_torus_h: bpy.props.FloatProperty(name="Mod Height", default=0.0)
|
||||||
|
|
||||||
# Direction
|
# Direction
|
||||||
flip_p: bpy.props.BoolProperty(name="Flip p", default=False)
|
flip_p: bpy.props.BoolProperty(name="Flip p", default=False)
|
||||||
flip_q: bpy.props.BoolProperty(name="Flip q", default=False)
|
flip_q: bpy.props.BoolProperty(name="Flip q", default=False)
|
||||||
|
|
||||||
|
# UI expand state (display-only, not serialised to animation)
|
||||||
|
ui_show_shape: bpy.props.BoolProperty(name="Shape", default=True)
|
||||||
|
ui_show_geometry: bpy.props.BoolProperty(name="Geometry", default=False)
|
||||||
|
ui_show_links: bpy.props.BoolProperty(name="Links & Phases", default=False)
|
||||||
|
ui_show_anim: bpy.props.BoolProperty(name="Animation Rates", default=True)
|
||||||
|
ui_show_material: bpy.props.BoolProperty(name="Material", default=True)
|
||||||
|
ui_show_colors: bpy.props.BoolProperty(name="Colors", default=False)
|
||||||
|
ui_show_trans: bpy.props.BoolProperty(name="Transition", default=False)
|
||||||
|
|
||||||
# Material
|
# Material
|
||||||
material_mode: bpy.props.EnumProperty(
|
material_mode: bpy.props.EnumProperty(
|
||||||
name="Material Mode",
|
name="Material Mode",
|
||||||
@@ -267,17 +342,29 @@ class KnotItem(bpy.types.PropertyGroup):
|
|||||||
return {
|
return {
|
||||||
"shape_type": self.shape_type,
|
"shape_type": self.shape_type,
|
||||||
"torus_p": self.torus_p,
|
"torus_p": self.torus_p,
|
||||||
|
"mod_torus_p": self.mod_torus_p,
|
||||||
"torus_q": self.torus_q,
|
"torus_q": self.torus_q,
|
||||||
|
"mod_torus_q": self.mod_torus_q,
|
||||||
"mobius_twists": self.mobius_twists,
|
"mobius_twists": self.mobius_twists,
|
||||||
|
"mod_mobius_twists": self.mod_mobius_twists,
|
||||||
"mobius_width": self.mobius_width,
|
"mobius_width": self.mobius_width,
|
||||||
|
"mod_mobius_width": self.mod_mobius_width,
|
||||||
"liss_kx": self.liss_kx,
|
"liss_kx": self.liss_kx,
|
||||||
|
"mod_liss_kx": self.mod_liss_kx,
|
||||||
"liss_ky": self.liss_ky,
|
"liss_ky": self.liss_ky,
|
||||||
|
"mod_liss_ky": self.mod_liss_ky,
|
||||||
"liss_kz": self.liss_kz,
|
"liss_kz": self.liss_kz,
|
||||||
|
"mod_liss_kz": self.mod_liss_kz,
|
||||||
"liss_amp": self.liss_amp,
|
"liss_amp": self.liss_amp,
|
||||||
|
"mod_liss_amp": self.mod_liss_amp,
|
||||||
"spiral_turns": self.spiral_turns,
|
"spiral_turns": self.spiral_turns,
|
||||||
|
"mod_spiral_turns": self.mod_spiral_turns,
|
||||||
"spiral_R": self.spiral_R,
|
"spiral_R": self.spiral_R,
|
||||||
|
"mod_spiral_R": self.mod_spiral_R,
|
||||||
"torus_R": self.torus_R,
|
"torus_R": self.torus_R,
|
||||||
|
"mod_torus_R": self.mod_torus_R,
|
||||||
"torus_r": self.torus_r,
|
"torus_r": self.torus_r,
|
||||||
|
"mod_torus_r": self.mod_torus_r,
|
||||||
"multiple_links": self.multiple_links,
|
"multiple_links": self.multiple_links,
|
||||||
"use_colors": self.use_colors,
|
"use_colors": self.use_colors,
|
||||||
"colorSet": self.colorSet,
|
"colorSet": self.colorSet,
|
||||||
@@ -325,6 +412,7 @@ class KnotGeneratorSettings(bpy.types.PropertyGroup):
|
|||||||
|
|
||||||
# Standalone booleans without an associated range or prob
|
# Standalone booleans without an associated range or prob
|
||||||
r_shape_type: bpy.props.BoolProperty(name="Randomize Shape Type", default=True)
|
r_shape_type: bpy.props.BoolProperty(name="Randomize Shape Type", default=True)
|
||||||
|
r_modulations: bpy.props.BoolProperty(name="Randomize Mods", default=True, description="Add random attenuverter modulations to the generated knots")
|
||||||
r_transition_easing: bpy.props.BoolProperty(name="Easing", default=False)
|
r_transition_easing: bpy.props.BoolProperty(name="Easing", default=False)
|
||||||
r_material: bpy.props.BoolProperty(
|
r_material: bpy.props.BoolProperty(
|
||||||
name="Randomize Material", default=True,
|
name="Randomize Material", default=True,
|
||||||
@@ -337,6 +425,15 @@ class KnotGeneratorSettings(bpy.types.PropertyGroup):
|
|||||||
allowed_materials: bpy.props.CollectionProperty(type=KnotAllowedMaterial)
|
allowed_materials: bpy.props.CollectionProperty(type=KnotAllowedMaterial)
|
||||||
allowed_materials_index: bpy.props.IntProperty(name="Index", default=0)
|
allowed_materials_index: bpy.props.IntProperty(name="Index", default=0)
|
||||||
|
|
||||||
|
# UI expand state for generator rand sub-boxes
|
||||||
|
ui_show_rand_shape: bpy.props.BoolProperty(name="Shape", default=True)
|
||||||
|
ui_show_rand_geo: bpy.props.BoolProperty(name="Geometry", default=False)
|
||||||
|
ui_show_rand_anim: bpy.props.BoolProperty(name="Animation", default=True)
|
||||||
|
ui_show_rand_mat: bpy.props.BoolProperty(name="Material", default=True)
|
||||||
|
ui_show_base: bpy.props.BoolProperty(name="Base Knot Defaults", default=False)
|
||||||
|
ui_show_generator: bpy.props.BoolProperty(name="Random Generator", default=True)
|
||||||
|
ui_show_rand_toggles: bpy.props.BoolProperty(name="Randomise Toggles", default=True)
|
||||||
|
|
||||||
|
|
||||||
# ── Integer range triplets ────────────────────────────────────────────────────
|
# ── Integer range triplets ────────────────────────────────────────────────────
|
||||||
_add_rand_int(KnotGeneratorSettings, "torus_p", "Revolutions (p)", 1, 8, rand_default=True, val_min=1)
|
_add_rand_int(KnotGeneratorSettings, "torus_p", "Revolutions (p)", 1, 8, rand_default=True, val_min=1)
|
||||||
|
|||||||
@@ -24,33 +24,47 @@ class KnotConfig(TypedDict, total=False):
|
|||||||
|
|
||||||
# ── Topology (Torus Knot) ────────────────────────────────────────────────
|
# ── Topology (Torus Knot) ────────────────────────────────────────────────
|
||||||
torus_p: int
|
torus_p: int
|
||||||
|
mod_torus_p: float
|
||||||
torus_q: int
|
torus_q: int
|
||||||
|
mod_torus_q: float
|
||||||
flip_p: bool
|
flip_p: bool
|
||||||
flip_q: bool
|
flip_q: bool
|
||||||
multiple_links: bool
|
multiple_links: bool
|
||||||
|
|
||||||
# ── Topology (Mobius) ────────────────────────────────────────────────────
|
# ── Topology (Mobius) ────────────────────────────────────────────────────
|
||||||
mobius_twists: int
|
mobius_twists: int
|
||||||
|
mod_mobius_twists: float
|
||||||
mobius_width: float
|
mobius_width: float
|
||||||
|
mod_mobius_width: float
|
||||||
|
|
||||||
# ── Topology (Lissajous 3D) ──────────────────────────────────────────────
|
# ── Topology (Lissajous 3D) ──────────────────────────────────────────────
|
||||||
liss_kx: int
|
liss_kx: int
|
||||||
|
mod_liss_kx: float
|
||||||
liss_ky: int
|
liss_ky: int
|
||||||
|
mod_liss_ky: float
|
||||||
liss_kz: int
|
liss_kz: int
|
||||||
|
mod_liss_kz: float
|
||||||
liss_amp: float
|
liss_amp: float
|
||||||
|
mod_liss_amp: float
|
||||||
|
|
||||||
# ── Topology (Spherical Spiral) ──────────────────────────────────────────
|
# ── Topology (Spherical Spiral) ──────────────────────────────────────────
|
||||||
spiral_turns: int
|
spiral_turns: int
|
||||||
|
mod_spiral_turns: float
|
||||||
spiral_R: float
|
spiral_R: float
|
||||||
|
mod_spiral_R: float
|
||||||
|
|
||||||
# ── Radii (Major/Minor mode) ──────────────────────────────────────────────
|
# ── Radii (Major/Minor mode) ──────────────────────────────────────────────
|
||||||
torus_R: float
|
torus_R: float
|
||||||
|
mod_torus_R: float
|
||||||
torus_r: float
|
torus_r: float
|
||||||
|
mod_torus_r: float
|
||||||
|
|
||||||
# ── Radii (Ext/Int mode) ─────────────────────────────────────────────────
|
# ── Radii (Ext/Int mode) ─────────────────────────────────────────────────
|
||||||
mode: str # 'MAJOR_MINOR' | 'EXT_INT'
|
mode: str # 'MAJOR_MINOR' | 'EXT_INT'
|
||||||
torus_eR: float
|
torus_eR: float
|
||||||
|
mod_torus_eR: float
|
||||||
torus_iR: float
|
torus_iR: float
|
||||||
|
mod_torus_iR: float
|
||||||
|
|
||||||
# ── Multipliers & phases ──────────────────────────────────────────────────
|
# ── Multipliers & phases ──────────────────────────────────────────────────
|
||||||
torus_u: int
|
torus_u: int
|
||||||
@@ -58,6 +72,7 @@ class KnotConfig(TypedDict, total=False):
|
|||||||
torus_rP: float
|
torus_rP: float
|
||||||
torus_sP: float
|
torus_sP: float
|
||||||
torus_h: float
|
torus_h: float
|
||||||
|
mod_torus_h: float
|
||||||
|
|
||||||
# ── Per-knot animation rates ──────────────────────────────────────────────
|
# ── Per-knot animation rates ──────────────────────────────────────────────
|
||||||
spin_phase_rate: float
|
spin_phase_rate: float
|
||||||
@@ -69,8 +84,11 @@ class KnotConfig(TypedDict, total=False):
|
|||||||
|
|
||||||
# ── Geometry ──────────────────────────────────────────────────────────────
|
# ── Geometry ──────────────────────────────────────────────────────────────
|
||||||
geo_extrude: float
|
geo_extrude: float
|
||||||
|
mod_geo_extrude: float
|
||||||
geo_offset: float
|
geo_offset: float
|
||||||
|
mod_geo_offset: float
|
||||||
geo_bDepth: float
|
geo_bDepth: float
|
||||||
|
mod_geo_bDepth: float
|
||||||
|
|
||||||
# ── Transition ────────────────────────────────────────────────────────────
|
# ── Transition ────────────────────────────────────────────────────────────
|
||||||
transition_frames: int
|
transition_frames: int
|
||||||
|
|||||||
+324
-197
@@ -5,12 +5,11 @@ All Blender UI classes for knot_animation:
|
|||||||
|
|
||||||
KNOT_UL_List — playlist UIList
|
KNOT_UL_List — playlist UIList
|
||||||
KNOT_UL_AllowedMaterialsList— generator filter UIList
|
KNOT_UL_AllowedMaterialsList— generator filter UIList
|
||||||
draw_knot_properties() — shared property layout helper
|
draw_knot_properties() — shared property layout helper (collapsible sections)
|
||||||
KNOT_PT_Panel — main N-panel (VIEW_3D > UI > AnimKnots)
|
KNOT_PT_Panel — main N-panel (VIEW_3D > UI > AnimKnots)
|
||||||
|
|
||||||
This module contains only layout code. It imports compute_playlist_duration
|
Every major block is collapsible via per-item or per-settings ui_show_* flags.
|
||||||
from handler.py for the live duration readout; it does not call any geometry
|
KnotItem flags are per-item so each playlist entry remembers its own state.
|
||||||
or material functions directly.
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -19,6 +18,38 @@ import bpy
|
|||||||
from .handler import compute_playlist_duration
|
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
|
# UILists
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -45,98 +76,105 @@ class KNOT_UL_AllowedMaterialsList(bpy.types.UIList):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def draw_knot_properties(layout, item) -> None:
|
def draw_knot_properties(layout, item) -> None:
|
||||||
"""Draw all KnotItem properties into *layout*.
|
"""Draw all KnotItem properties using collapsible per-item sections."""
|
||||||
|
|
||||||
Used by both the main panel (selected item) and the generator base-knot
|
# ── Shape ──────────────────────────────────────────────────────────────
|
||||||
section so the layout stays in sync automatically.
|
box, open_ = _section(layout, item, "ui_show_shape", "Shape", 'CURVE_PATH')
|
||||||
"""
|
if open_:
|
||||||
layout.prop(item, "shape_type")
|
box.prop(item, "shape_type")
|
||||||
|
|
||||||
if item.shape_type == 'TORUS_KNOT':
|
if item.shape_type == 'TORUS_KNOT':
|
||||||
row = layout.row(align=True)
|
draw_prop_with_mod(box, item, "torus_p", "mod_torus_p")
|
||||||
row.prop(item, "torus_p")
|
draw_prop_with_mod(box, item, "torus_q", "mod_torus_q")
|
||||||
row.prop(item, "torus_q")
|
row = box.row(align=True)
|
||||||
row = layout.row(align=True)
|
row.prop(item, "flip_p", toggle=True, icon='ARROW_LEFTRIGHT')
|
||||||
row.prop(item, "flip_p", toggle=True, icon='ARROW_LEFTRIGHT')
|
row.prop(item, "flip_q", 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")
|
||||||
|
|
||||||
layout.prop(item, "mode")
|
elif item.shape_type == 'MOBIUS':
|
||||||
if item.mode == 'MAJOR_MINOR':
|
draw_prop_with_mod(box, item, "mobius_twists", "mod_mobius_twists")
|
||||||
row = layout.row(align=True)
|
draw_prop_with_mod(box, item, "mobius_width", "mod_mobius_width")
|
||||||
row.prop(item, "torus_R")
|
|
||||||
row.prop(item, "torus_r")
|
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:
|
else:
|
||||||
row = layout.row(align=True)
|
box.prop(item, "project_material", text="Material")
|
||||||
row.prop(item, "torus_eR")
|
|
||||||
row.prop(item, "torus_iR")
|
|
||||||
|
|
||||||
elif item.shape_type == 'MOBIUS':
|
# ── Colors ───────────────────────────────────────────────────────────────
|
||||||
row = layout.row(align=True)
|
box, open_ = _section(layout, item, "ui_show_colors", "Colors", 'COLOR')
|
||||||
row.prop(item, "mobius_twists")
|
if open_:
|
||||||
row.prop(item, "mobius_width")
|
box.prop(item, "use_colors")
|
||||||
|
if item.use_colors:
|
||||||
|
row = box.row(align=True)
|
||||||
|
row.prop(item, "colorSet")
|
||||||
|
row.prop(item, "random_colors")
|
||||||
|
|
||||||
elif item.shape_type == 'LISSAJOUS':
|
# ── Transition ───────────────────────────────────────────────────────────
|
||||||
row = layout.row(align=True)
|
box, open_ = _section(layout, item, "ui_show_trans", "Transition", 'MOD_TIME')
|
||||||
row.prop(item, "liss_kx")
|
if open_:
|
||||||
row.prop(item, "liss_ky")
|
box.prop(item, "transition_frames")
|
||||||
row.prop(item, "liss_kz")
|
if item.transition_frames > 0:
|
||||||
layout.prop(item, "liss_amp")
|
box.prop(item, "transition_easing")
|
||||||
|
|
||||||
elif item.shape_type == 'SPIRAL':
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop(item, "spiral_turns")
|
|
||||||
row.prop(item, "spiral_R")
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop(item, "geo_extrude")
|
|
||||||
row.prop(item, "geo_offset")
|
|
||||||
layout.prop(item, "geo_bDepth")
|
|
||||||
layout.prop(item, "torus_h")
|
|
||||||
|
|
||||||
col = layout.column(align=True)
|
|
||||||
col.prop(item, "multiple_links")
|
|
||||||
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")
|
|
||||||
|
|
||||||
anim_box = layout.box()
|
|
||||||
anim_box.label(text="Animation Rates", icon='ANIM')
|
|
||||||
anim_box.prop(item, "cycle_rate")
|
|
||||||
col = anim_box.column(align=True)
|
|
||||||
col.prop(item, "spin_phase_rate")
|
|
||||||
col.prop(item, "rev_phase_rate")
|
|
||||||
col.prop(item, "height_rate")
|
|
||||||
row = anim_box.row(align=True)
|
|
||||||
row.prop(item, "scale_rate")
|
|
||||||
row.prop(item, "scale_amplitude")
|
|
||||||
|
|
||||||
mat_box = layout.box()
|
|
||||||
mat_box.label(text="Material Options", icon='MATERIAL')
|
|
||||||
mat_box.prop(item, "material_mode", expand=True)
|
|
||||||
if item.material_mode == 'PRESET':
|
|
||||||
mat_box.prop(item, "shader_id")
|
|
||||||
col = mat_box.column(align=True)
|
|
||||||
col.prop(item, "preset_color")
|
|
||||||
col.prop(item, "preset_roughness", slider=True)
|
|
||||||
col.prop(item, "preset_metallic", slider=True)
|
|
||||||
col.prop(item, "preset_emission_strength")
|
|
||||||
else:
|
|
||||||
mat_box.prop(item, "project_material", text="Material")
|
|
||||||
|
|
||||||
layout.prop(item, "use_colors")
|
|
||||||
if item.use_colors:
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop(item, "colorSet")
|
|
||||||
row.prop(item, "random_colors")
|
|
||||||
|
|
||||||
trans_box = layout.box()
|
|
||||||
trans_box.label(text="Transition to Next Knot", icon='MOD_TIME')
|
|
||||||
trans_box.prop(item, "transition_frames")
|
|
||||||
if item.transition_frames > 0:
|
|
||||||
trans_box.prop(item, "transition_easing")
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -144,7 +182,7 @@ def draw_knot_properties(layout, item) -> None:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class KNOT_PT_Panel(bpy.types.Panel):
|
class KNOT_PT_Panel(bpy.types.Panel):
|
||||||
bl_label = "AnimKnots Configuration"
|
bl_label = "AnimKnots"
|
||||||
bl_idname = "KNOT_PT_panel"
|
bl_idname = "KNOT_PT_panel"
|
||||||
bl_space_type = 'VIEW_3D'
|
bl_space_type = 'VIEW_3D'
|
||||||
bl_region_type = 'UI'
|
bl_region_type = 'UI'
|
||||||
@@ -153,131 +191,220 @@ class KNOT_PT_Panel(bpy.types.Panel):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
scene = context.scene
|
scene = context.scene
|
||||||
|
glob = scene.knot_globals
|
||||||
|
gen = scene.knot_generator
|
||||||
|
|
||||||
# ── Playlist ────────────────────────────────────────────────────────
|
# ── Playlist ─────────────────────────────────────────────────────────
|
||||||
row = layout.row()
|
pl_box = layout.box()
|
||||||
row.template_list("KNOT_UL_List", "", scene, "knot_list",
|
pl_row = pl_box.row()
|
||||||
scene, "knot_list_index")
|
pl_row.prop(glob, "ui_show_playlist",
|
||||||
col = row.column(align=True)
|
icon='TRIA_DOWN' if glob.ui_show_playlist else 'TRIA_RIGHT',
|
||||||
col.operator("knot.add_item", text="", icon='ADD')
|
icon_only=True, emboss=False)
|
||||||
col.operator("knot.remove_item",text="", icon='REMOVE')
|
pl_row.label(text="Playlist", icon='NLA')
|
||||||
col.separator()
|
if glob.ui_show_playlist:
|
||||||
col.operator("knot.move_item", text="", icon='TRIA_UP').direction = 'UP'
|
row = pl_box.row()
|
||||||
col.operator("knot.move_item", text="", icon='TRIA_DOWN').direction = 'DOWN'
|
row.template_list("KNOT_UL_List", "", scene, "knot_list",
|
||||||
|
scene, "knot_list_index")
|
||||||
layout.operator("knot.populate", icon='FILE_REFRESH')
|
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):
|
if 0 <= scene.knot_list_index < len(scene.knot_list):
|
||||||
item = scene.knot_list[scene.knot_list_index]
|
item = scene.knot_list[scene.knot_list_index]
|
||||||
box = layout.box()
|
ke_box = layout.box()
|
||||||
box.prop(item, "name")
|
ke_row = ke_box.row()
|
||||||
draw_knot_properties(box, item)
|
ke_row.prop(glob, "ui_show_knot_edit",
|
||||||
layout.operator("knot.bake_preview", icon='FILE_TICK')
|
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 ─────────────────────────────────────────────────
|
# ── Global Settings ───────────────────────────────────────────────────
|
||||||
layout.separator()
|
|
||||||
gb = layout.box()
|
gb = layout.box()
|
||||||
gb.label(text="Global Settings", icon='WORLD')
|
gb_row = gb.row()
|
||||||
glob = scene.knot_globals
|
gb_row.prop(glob, "ui_show_global",
|
||||||
gb.prop(glob, "frames_per_knot")
|
icon='TRIA_DOWN' if glob.ui_show_global else 'TRIA_RIGHT',
|
||||||
gb.prop(glob, "resolution")
|
icon_only=True, emboss=False)
|
||||||
gb.prop(glob, "bevel_resolution")
|
gb_row.label(text="Global Settings", icon='WORLD')
|
||||||
gb.prop(glob, "knot_scale")
|
|
||||||
|
|
||||||
n_knots = len(scene.knot_list)
|
if glob.ui_show_global:
|
||||||
if n_knots > 0:
|
gb.prop(glob, "knot_scale")
|
||||||
total_frames = compute_playlist_duration(scene)
|
gb.prop(glob, "frames_per_knot")
|
||||||
fps = max(1, scene.render.fps)
|
|
||||||
secs = total_frames / fps
|
|
||||||
dur_row = gb.row(align=True)
|
|
||||||
dur_row.label(
|
|
||||||
text=f"Playlist: {total_frames} fr / {secs:.1f}s ({n_knots} knots)",
|
|
||||||
icon='TIME')
|
|
||||||
fit_row = gb.row(align=True)
|
|
||||||
fit_row.operator("knot.fit_timeline", icon='PREVIEW_RANGE', text="Fit Timeline")
|
|
||||||
fit_row.operator("knot.fit_playlist", icon='NLA_PUSHDOWN', text="Fit Playlist")
|
|
||||||
|
|
||||||
gb.separator()
|
# Resolutions
|
||||||
gb.label(text="Playback Control", icon='PLAY')
|
res_box = gb.box()
|
||||||
gb.prop(glob, "global_speed")
|
res_box.label(text="Resolutions (Preview / Render)", icon='RESTRICT_VIEW_OFF')
|
||||||
gb.prop(glob, "animation_phase")
|
row = res_box.row(align=True)
|
||||||
gb.prop(glob, "reactivity_factor", slider=True)
|
row.prop(glob, "preview_resolution", text="Curve")
|
||||||
gb.label(text="↑ Keyframe or drive for reactivity", icon='INFO')
|
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="")
|
||||||
|
|
||||||
row = gb.row(align=True)
|
# Visual Effects & Mods
|
||||||
row.prop(scene, "frame_end", text="Total Frames")
|
fx_box = gb.box()
|
||||||
row.prop(scene.render, "fps", text="FPS")
|
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")
|
||||||
|
|
||||||
# ── Random Generator ────────────────────────────────────────────────
|
row = fx_box.row(align=True)
|
||||||
layout.separator()
|
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_box = layout.box()
|
||||||
gen_box.label(text="Random Knot Generator", icon='GROUP')
|
gen_hdr = gen_box.row()
|
||||||
gen = scene.knot_generator
|
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')
|
||||||
|
|
||||||
gen_box.prop(gen, "num_knots")
|
if gen.ui_show_generator:
|
||||||
|
gen_box.prop(gen, "num_knots")
|
||||||
|
|
||||||
base_box = gen_box.box()
|
# · Base Knot Defaults ·
|
||||||
base_box.label(text="Base Defaults", icon='PRESET')
|
base_box = gen_box.box()
|
||||||
draw_knot_properties(base_box, gen.base_knot)
|
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)
|
||||||
|
|
||||||
rand_box = gen_box.box()
|
# · Randomise Toggles (collapsible container) ·
|
||||||
rand_box.label(text="Randomize Toggles", icon='MODIFIER')
|
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')
|
||||||
|
|
||||||
def draw_rand(prop):
|
if gen.ui_show_rand_toggles:
|
||||||
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):
|
def draw_rand(prop):
|
||||||
row = rand_box.row(align=True)
|
row = rand_box.row(align=True)
|
||||||
row.prop(gen, f"r_{prop}")
|
row.prop(gen, f"r_{prop}")
|
||||||
if getattr(gen, f"r_{prop}") and hasattr(gen, f"prob_{prop}"):
|
if getattr(gen, f"r_{prop}"):
|
||||||
row.prop(gen, f"prob_{prop}")
|
row.prop(gen, f"min_{prop}")
|
||||||
|
row.prop(gen, f"max_{prop}")
|
||||||
|
|
||||||
rand_box.prop(gen, "r_shape_type")
|
def draw_rand_bool(prop):
|
||||||
draw_rand("torus_p"); draw_rand("torus_q")
|
row = rand_box.row(align=True)
|
||||||
draw_rand_bool("flip_p"); draw_rand_bool("flip_q")
|
row.prop(gen, f"r_{prop}")
|
||||||
draw_rand_bool("mode")
|
if getattr(gen, f"r_{prop}") and hasattr(gen, f"prob_{prop}"):
|
||||||
draw_rand("torus_R"); draw_rand("torus_r")
|
row.prop(gen, f"prob_{prop}")
|
||||||
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")
|
|
||||||
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("spin_phase_rate")
|
|
||||||
draw_rand_bool("use_colors"); draw_rand_bool("colorSet"); draw_rand_bool("random_colors")
|
|
||||||
draw_rand("transition_frames"); draw_rand_bool("transition_easing")
|
|
||||||
|
|
||||||
rand_box.separator()
|
# ·· Shape ··
|
||||||
rand_box.label(text="Animation Rates", icon='ANIM')
|
s_box = rand_box.box()
|
||||||
draw_rand("cycle_rate"); draw_rand("spin_phase_rate")
|
s_row = s_box.row()
|
||||||
draw_rand("rev_phase_rate"); draw_rand("height_rate")
|
s_row.prop(gen, "ui_show_rand_shape",
|
||||||
draw_rand("scale_rate"); draw_rand("scale_amplitude")
|
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")
|
||||||
|
|
||||||
row = rand_box.row(align=True)
|
# ·· Geometry & Links ··
|
||||||
row.prop(gen, "r_material")
|
g_box = rand_box.box()
|
||||||
if gen.r_material:
|
g_row = g_box.row()
|
||||||
row.prop(gen, "r_preset_params", text="Random Parameters")
|
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")
|
||||||
|
|
||||||
filter_box = rand_box.box()
|
# ·· Animation Rates ··
|
||||||
filter_box.label(text="Allowed Materials & Presets:")
|
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")
|
||||||
|
|
||||||
row_sync = filter_box.row()
|
# ·· Material ··
|
||||||
row_sync.template_list(
|
m_box = rand_box.box()
|
||||||
"KNOT_UL_AllowedMaterialsList", "",
|
m_row = m_box.row()
|
||||||
gen, "allowed_materials",
|
m_row.prop(gen, "ui_show_rand_mat",
|
||||||
gen, "allowed_materials_index",
|
icon='TRIA_DOWN' if gen.ui_show_rand_mat else 'TRIA_RIGHT',
|
||||||
rows=5)
|
icon_only=True, emboss=False)
|
||||||
col_btn = row_sync.column(align=True)
|
m_row.label(text="Material", icon='MATERIAL')
|
||||||
col_btn.operator("knot.sync_generator_materials", icon='FILE_REFRESH', text="")
|
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)
|
||||||
|
row_sync.column(align=True).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')
|
||||||
|
|
||||||
if len(gen.allowed_materials) == 0:
|
gen_box.operator("knot.generate_random", icon='PLAY')
|
||||||
filter_box.label(text="Click Sync to load project materials & presets", icon='INFO')
|
|
||||||
|
|
||||||
gen_box.operator("knot.generate_random", icon='PLAY')
|
|
||||||
|
|||||||
Reference in New Issue
Block a user