|
|
|
|
@ -1,16 +1,16 @@ |
|
|
|
|
import math |
|
|
|
|
|
|
|
|
|
# me - this DAT |
|
|
|
|
# scriptOp - the OP which is cooking |
|
|
|
|
# |
|
|
|
|
# press 'Setup Parameters' in the OP to call this function to re-create the parameters. |
|
|
|
|
|
|
|
|
|
def onSetupParameters(scriptOp): |
|
|
|
|
page = scriptOp.appendCustomPage('Custom') |
|
|
|
|
page.appendFloat('Valuea', label='Value A') |
|
|
|
|
page.appendFloat('Valueb', label='Value B') |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
def onCook(scriptOp): |
|
|
|
|
scriptOp.clear() |
|
|
|
|
# Define Table Header with position and rotation columns |
|
|
|
|
scriptOp.appendRow(['text', 'P(X)', 'P(Y)', 'P(Z)', 'R(X)', 'R(Y)', 'R(Z)']) |
|
|
|
|
|
|
|
|
|
# --- Fetch External Parameters --- |
|
|
|
|
@ -18,51 +18,90 @@ def onCook(scriptOp): |
|
|
|
|
if not const_op: |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
radius_scale = const_op['ring_scale'] |
|
|
|
|
rotate_speed = const_op['rotate_speed'] |
|
|
|
|
# text_space: used to define the angular gap between characters |
|
|
|
|
text_space = const_op['text_space'] |
|
|
|
|
# Fix for L22: CHOPs are not directly iterable. |
|
|
|
|
# We check for channel existence using the [] accessor and a fallback. |
|
|
|
|
def get_chan(chan_name, default=1.0): |
|
|
|
|
c = const_op[chan_name] |
|
|
|
|
return c.eval() if c is not None else default |
|
|
|
|
|
|
|
|
|
radius_scale = get_chan('ring_scale', 1.0) |
|
|
|
|
rotate_speed = get_chan('rotate_speed', 1.0) |
|
|
|
|
text_space = get_chan('text_space', 0.5) |
|
|
|
|
|
|
|
|
|
arc_table = op('arc_table') |
|
|
|
|
info_table = op('info_table') |
|
|
|
|
scaled_pos_op = op('scaled_positions') |
|
|
|
|
ring_const = op('/project1/RingConstant') |
|
|
|
|
draw_arc=parent().par.Drawarc.eval() if hasattr(parent().par, 'Drawarc') else False |
|
|
|
|
|
|
|
|
|
# Safety check for required info |
|
|
|
|
if not ring_const: |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
# Assuming ring_const is a Constant CHOP as well |
|
|
|
|
def get_remote_chan(op_ref, chan_name, default=1.0): |
|
|
|
|
if not op_ref: return default |
|
|
|
|
c = op_ref[chan_name] |
|
|
|
|
return c.eval() if c is not None else default |
|
|
|
|
|
|
|
|
|
f_size = get_remote_chan(ring_const, 'FontSize', 1.0) |
|
|
|
|
i_size = get_remote_chan(ring_const, 'InfoFontSize', 1.0) |
|
|
|
|
info_scale = i_size / f_size if f_size != 0 else 1.0 |
|
|
|
|
|
|
|
|
|
if arc_table: |
|
|
|
|
arc_table.clear() |
|
|
|
|
arc_table.appendRow(['tx', 'ty', 'tz','rx', 'ry', 'rz','scale']) |
|
|
|
|
arc_table.appendRow(['tx', 'ty', 'tz','rx', 'ry', 'rz','scale', 'start1', 'end1', 'start2', 'end2']) |
|
|
|
|
|
|
|
|
|
if info_table: |
|
|
|
|
info_table.clear() |
|
|
|
|
info_table.appendRow(['text', 'tx', 'ty', 'tz', 'rx', 'ry', 'rz']) |
|
|
|
|
|
|
|
|
|
for source in scriptOp.inputs: |
|
|
|
|
# Safety check: Input must exist and have at least a header and one data row |
|
|
|
|
if not source or source.numRows < 2: |
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
# Iterate through the rows of the source DAT |
|
|
|
|
for i, row in enumerate(source.rows()[1:]): |
|
|
|
|
|
|
|
|
|
full_string = row[0].val |
|
|
|
|
# Ensure index exists for substring column |
|
|
|
|
sub_string = row[9].val if len(row) > 9 else "" |
|
|
|
|
|
|
|
|
|
if not full_string: |
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
str_len = len(full_string) |
|
|
|
|
sub_str_len = len(sub_string) |
|
|
|
|
|
|
|
|
|
# Fetch speed variation from the speed_variation CHOP |
|
|
|
|
# Fetch speed variation from CHOP |
|
|
|
|
speed_op = op('speed_variation') |
|
|
|
|
speed_variation = speed_op[0][i] if speed_op and i < speed_op.numSamples else 0 |
|
|
|
|
speed_variation = 0 |
|
|
|
|
if speed_op: |
|
|
|
|
# Use first channel, i-th sample |
|
|
|
|
if i < speed_op.numSamples: |
|
|
|
|
speed_variation = speed_op[0][i] |
|
|
|
|
|
|
|
|
|
# Calculate time-based rotation offset (in radians) |
|
|
|
|
time_offset = absTime.seconds * rotate_speed *(1.0+ abs(speed_variation))/10.0 |
|
|
|
|
# Time calculation |
|
|
|
|
time_offset = absTime.seconds * rotate_speed * (1.0 + abs(speed_variation)) / 10.0 |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
center = [op('scaled_positions')[0][i], op('scaled_positions')[1][i], op('scaled_positions')[2][i]] |
|
|
|
|
axis = [float(row[5]), float(row[6]), float(row[7])] |
|
|
|
|
# Use scaled_positions if it exists, otherwise default to origin |
|
|
|
|
if scaled_pos_op and i < scaled_pos_op.numSamples: |
|
|
|
|
center = [scaled_pos_op[0][i], scaled_pos_op[1][i], scaled_pos_op[2][i]] |
|
|
|
|
else: |
|
|
|
|
center = [0, 0, 0] |
|
|
|
|
|
|
|
|
|
axis = [float(row[5]), float(row[6]), float(row[7])] |
|
|
|
|
radius = float(row[8]) * radius_scale |
|
|
|
|
except (ValueError, TypeError, IndexError): |
|
|
|
|
continue |
|
|
|
|
# --- Calculate Orthogonal Basis for the Ring Plane --- |
|
|
|
|
|
|
|
|
|
# --- Calculate Orthogonal Basis --- |
|
|
|
|
ax, ay, az = axis |
|
|
|
|
mag = math.sqrt(ax*ax + ay*ay + az*az) |
|
|
|
|
if mag == 0: |
|
|
|
|
ax, ay, az = 0, 1, 0 |
|
|
|
|
else: |
|
|
|
|
ax, ay, az = ax/mag, ay/mag, az/mag |
|
|
|
|
|
|
|
|
|
if abs(ax) < 0.9: |
|
|
|
|
tx, ty, tz = 1, 0, 0 |
|
|
|
|
else: |
|
|
|
|
@ -72,75 +111,113 @@ def onCook(scriptOp): |
|
|
|
|
uy = az*tx - ax*tz |
|
|
|
|
uz = ax*ty - ay*tx |
|
|
|
|
umag = math.sqrt(ux*ux + uy*uy + uz*uz) |
|
|
|
|
if umag == 0: umag = 1 |
|
|
|
|
ux, uy, uz = ux/umag, uy/umag, uz/umag |
|
|
|
|
|
|
|
|
|
vx = ay*uz - az*uy |
|
|
|
|
vy = az*ux - ax*uz |
|
|
|
|
vz = ax*uy - ay*ux |
|
|
|
|
|
|
|
|
|
# --- Calculate Arc Logic --- |
|
|
|
|
# Instead of forcing (i/str_len)*2*pi, we use text_space to determine the gap. |
|
|
|
|
# If the total arc (gap * characters) < 2*pi, it remains an arc. |
|
|
|
|
angular_step = text_space / (radius if radius > 0 else 1) |
|
|
|
|
# angular_step = radius*math.pi*2.0/text_space if radius>0 else math.pi*2.0/str_len |
|
|
|
|
total= math.floor(math.pi*2.0/(angular_step)) |
|
|
|
|
count=0 |
|
|
|
|
print(f"Processing string with length {str_len}, total positions: {total}") |
|
|
|
|
# Build the table character by character |
|
|
|
|
while count<total: |
|
|
|
|
# for j in range(str_len): |
|
|
|
|
angular_step = text_space / (radius if radius > 0.1 else 0.1) |
|
|
|
|
|
|
|
|
|
# SAFETY: Limit total iterations |
|
|
|
|
total = int(min(math.floor((math.pi * 2.0) / (angular_step if angular_step > 0.01 else 0.01)), 500)) |
|
|
|
|
total = max(total, str_len + sub_str_len*.5) |
|
|
|
|
|
|
|
|
|
arc_len = round((total - (str_len + sub_str_len*.5)) / 2) |
|
|
|
|
total= str_len + arc_len*2 + sub_str_len |
|
|
|
|
|
|
|
|
|
angle = time_offset |
|
|
|
|
|
|
|
|
|
# arc1_start_angle=str_len*angular_step |
|
|
|
|
# arc1_end_angle=arc1_start_angle + arc_len*angular_step |
|
|
|
|
|
|
|
|
|
# arc2_start_angle=arc1_end_angle+(sub_str_len*angular_step*info_scale*.5) |
|
|
|
|
# arc2_end_angle=arc2_start_angle + arc_len*angular_step |
|
|
|
|
|
|
|
|
|
arc1_start_angle=angular_step * str_len #+time_offset%(2*math.pi) |
|
|
|
|
arc1_end_angle=arc1_start_angle + arc_len*angular_step |
|
|
|
|
|
|
|
|
|
arc2_start_angle=arc1_end_angle+((sub_str_len)*angular_step*info_scale*.5) |
|
|
|
|
arc2_end_angle=math.pi*2.0 #arc2_start_angle + arc_len*angular_step |
|
|
|
|
|
|
|
|
|
for count in range(total): |
|
|
|
|
is_sub_str_zone = (count >= str_len + arc_len) and (count < str_len + arc_len + sub_str_len) |
|
|
|
|
|
|
|
|
|
# angle = (index * step) + time_offset |
|
|
|
|
angle = ((count) * angular_step) + time_offset |
|
|
|
|
step_mult = info_scale*.5 if is_sub_str_zone else 1.0 |
|
|
|
|
angle += (angular_step * step_mult) |
|
|
|
|
|
|
|
|
|
cos_a = math.cos(angle) * radius |
|
|
|
|
sin_a = math.sin(angle) * radius |
|
|
|
|
|
|
|
|
|
# Outward vector relative to center |
|
|
|
|
off_x = (ux * cos_a) + (vx * sin_a) |
|
|
|
|
off_y = (uy * cos_a) + (vy * sin_a) |
|
|
|
|
off_z = (uz * cos_a) + (vz * sin_a) |
|
|
|
|
|
|
|
|
|
# Final World Position |
|
|
|
|
px = center[0] + off_x + parent().par.Keywordpositionx |
|
|
|
|
py = center[1] + off_y + parent().par.Keywordpositiony |
|
|
|
|
pz = center[2] + off_z + parent().par.Keywordpositionz |
|
|
|
|
kpx = getattr(parent().par, 'Keywordpositionx', 0) |
|
|
|
|
kpy = getattr(parent().par, 'Keywordpositiony', 0) |
|
|
|
|
kpz = getattr(parent().par, 'Keywordpositionz', 0) |
|
|
|
|
|
|
|
|
|
px = center[0] + off_x + kpx |
|
|
|
|
py = center[1] + off_y + kpy |
|
|
|
|
pz = center[2] + off_z + kpz |
|
|
|
|
# px=off_x + kpx |
|
|
|
|
# py=off_y + kpy |
|
|
|
|
# pz=off_z + kpz |
|
|
|
|
|
|
|
|
|
# --- Calculate Rotations to face outside --- |
|
|
|
|
dist = math.sqrt(off_x**2 + off_y**2 + off_z**2) |
|
|
|
|
if dist > 0: |
|
|
|
|
nx, ny, nz = off_x/dist, off_y/dist, off_z/dist |
|
|
|
|
else: |
|
|
|
|
nx, ny, nz = 0, 0, 1 |
|
|
|
|
nx, ny, nz = (off_x/dist, off_y/dist, off_z/dist) if dist > 0 else (0,0,1) |
|
|
|
|
|
|
|
|
|
char_ry = math.degrees(math.atan2(nx, nz)) |
|
|
|
|
char_rx = math.degrees(math.asin(-ny)) + 90 |
|
|
|
|
char_rz = 0 |
|
|
|
|
char_rx = math.degrees(math.asin(max(-1, min(1, -ny)))) + 90 |
|
|
|
|
|
|
|
|
|
tan_x = (ux * -math.sin(angle)) + (vx * math.cos(angle)) |
|
|
|
|
tan_y = (uy * -math.sin(angle)) + (vy * math.cos(angle)) |
|
|
|
|
tan_z = (uz * -math.sin(angle)) + (vz * math.cos(angle)) |
|
|
|
|
|
|
|
|
|
line_ry = math.degrees(math.atan2(tan_x, tan_z)) - 90 |
|
|
|
|
line_rx = math.degrees(math.asin(-tan_y)) |
|
|
|
|
line_rx = math.degrees(math.asin(max(-1, min(1, -tan_y)))) |
|
|
|
|
|
|
|
|
|
# ry = math.degrees(math.atan2(nx, nz)) |
|
|
|
|
# rx = math.degrees(math.asin(-ny))-90 |
|
|
|
|
# rz = 0 |
|
|
|
|
|
|
|
|
|
if count>=str_len: |
|
|
|
|
scale= radius*angular_step if radius>0 else 1 |
|
|
|
|
arc_table.appendRow([px, py, pz, line_rx, line_ry, 0, scale*0.5]) |
|
|
|
|
# scriptOp.appendRow(['-', px, py, pz, char_rx, char_ry, 0]) |
|
|
|
|
else: |
|
|
|
|
if count==0: |
|
|
|
|
pi=math.pi/2.0 |
|
|
|
|
two_pi=math.pi*2.0 |
|
|
|
|
half_pi=math.pi*0.5 |
|
|
|
|
arc_table.appendRow([center[0]+ kpx, center[1]+ kpy, center[2]+ kpz, |
|
|
|
|
90, time_offset/math.pi*180.0, 0, radius, |
|
|
|
|
(arc1_start_angle)/two_pi, |
|
|
|
|
(arc1_end_angle)/two_pi, |
|
|
|
|
(arc2_start_angle)/two_pi, |
|
|
|
|
(arc2_end_angle)/two_pi, |
|
|
|
|
]) # Header for arc table |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if count < str_len: |
|
|
|
|
char = full_string[count] |
|
|
|
|
scriptOp.appendRow([char, px, py, pz, char_rx, char_ry, 0]) |
|
|
|
|
# arc_table.appendRow([px, py, pz, rx, ry, rz, 1.0]) |
|
|
|
|
|
|
|
|
|
count+=1 |
|
|
|
|
elif count < str_len + arc_len: |
|
|
|
|
continue |
|
|
|
|
# if arc_table and draw_arc: |
|
|
|
|
# scale = (radius * angular_step * 0.5) if radius > 0 else 0.1 |
|
|
|
|
# arc_table.appendRow([px, py, pz, line_rx, line_ry, 0, scale]) |
|
|
|
|
|
|
|
|
|
elif count < str_len + arc_len + sub_str_len: |
|
|
|
|
char_idx = count - (str_len + arc_len) |
|
|
|
|
if char_idx < len(sub_string): |
|
|
|
|
char = sub_string[char_idx] |
|
|
|
|
if info_table: |
|
|
|
|
# continue |
|
|
|
|
info_table.appendRow([char, px, py, pz, char_rx, char_ry, 0]) |
|
|
|
|
|
|
|
|
|
else: |
|
|
|
|
continue |
|
|
|
|
# if arc_table and draw_arc: |
|
|
|
|
# scale = (radius * angular_step * 0.5) if radius > 0 else 0.1 |
|
|
|
|
# arc_table.appendRow([px, py, pz, line_rx, line_ry, 0, scale]) |
|
|
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
def onGetCookLevel(scriptOp): |
|
|
|
|
return CookLevel.ALWAYS |