Skip to content

Commit 4b35eb5

Browse files
authored
Add files via upload
1 parent f385216 commit 4b35eb5

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import pyvista as pv
2+
import numpy as np
3+
import struct
4+
5+
def load_ff7_model(filename):
6+
with open(filename, 'rb') as f:
7+
data = bytearray(f.read())
8+
num_v = struct.unpack('<I', data[12:16])[0]
9+
num_n = struct.unpack('<I', data[16:20])[0]
10+
num_t = struct.unpack('<I', data[24:28])[0]
11+
num_c = struct.unpack('<I', data[28:32])[0]
12+
13+
vertex_start = 128
14+
verts = np.frombuffer(data[vertex_start : vertex_start + (num_v * 12)], dtype=np.float32).reshape(-1, 3)
15+
16+
offset = 128 + (num_v * 12) + (num_n * 24) + (num_t * 8)
17+
color_raw = data[offset : offset + (num_c * 4)]
18+
color_data = np.frombuffer(color_raw, dtype=np.uint8).reshape(-1, 4)
19+
rgb = color_data[:, [2, 1, 0]].copy()
20+
21+
return verts, rgb, data, offset, num_c
22+
23+
# --- Global State ---
24+
FILE_NAME = 'ruam'
25+
verts, current_rgb, full_raw_data, color_offset, color_count = load_ff7_model(FILE_NAME)
26+
original_rgb = current_rgb.copy()
27+
modified_mask = np.zeros(color_count, dtype=bool)
28+
29+
colors = [
30+
np.array([156, 114, 102], dtype=np.int16),
31+
np.array([200, 150, 120], dtype=np.int16)
32+
]
33+
active_slot = 0
34+
restrict_changes = True
35+
point_size_val = 100.0
36+
37+
plotter = pv.Plotter(window_size=[1100, 800])
38+
cloud = pv.PolyData(verts)
39+
cloud['colors'] = current_rgb
40+
plotter.background_color = "white"
41+
42+
def refresh_mesh():
43+
cloud['colors'] = current_rgb
44+
plotter.add_mesh(cloud, scalars='colors', rgb=True, render_points_as_spheres=True,
45+
point_size=point_size_val, name="model")
46+
47+
# --- Dynamic UI Updates ---
48+
49+
def update_color_displays():
50+
"""Updates the numeric labels and the color box color."""
51+
gui_x = 20
52+
labels = ["R", "G", "B"]
53+
54+
for i in range(2):
55+
y_offset = 740 if i == 0 else 600
56+
# Update Text Labels (using 'name' ensures it replaces the old text instantly)
57+
for c in range(3):
58+
y = y_offset - (c * 25)
59+
plotter.add_text(f"{labels[c]}: {colors[i][c]}", position=(gui_x, y),
60+
font_size=9, color="black", name=f"txt_{i}_{c}")
61+
62+
# Update the Large Color Box
63+
# We recreate the widget only when the color changes to avoid stacking
64+
border = "yellow" if active_slot == i else "black"
65+
plotter.add_checkbox_button_widget(
66+
lambda b, idx=i: set_active_selection(idx),
67+
value=True, position=(135, y_offset - 45), size=65,
68+
color_on=colors[i].astype(float)/255.0,
69+
color_off=colors[i].astype(float)/255.0,
70+
background_color=border
71+
)
72+
73+
def adjust_rgb(slot, channel, delta):
74+
"""Adjusts values and updates only what is necessary (Instant)."""
75+
colors[slot][channel] = np.clip(colors[slot][channel] + delta, 0, 255)
76+
update_color_displays()
77+
78+
def set_active_selection(slot):
79+
global active_slot
80+
active_slot = slot
81+
update_color_displays()
82+
83+
# --- Initial UI Construction (Run ONCE) ---
84+
85+
def build_static_ui():
86+
gui_x = 20
87+
# 1. Create adjustment buttons once
88+
for i in range(2):
89+
y_offset = 740 if i == 0 else 600
90+
for channel in range(3):
91+
y = y_offset - (channel * 25)
92+
93+
# Plus (White background to hide gray box)
94+
plotter.add_checkbox_button_widget(lambda b, s=i, c=channel: adjust_rgb(s, c, 5),
95+
position=(gui_x + 65, y), size=18, color_on="white", color_off="white", background_color="white")
96+
plotter.add_text("+", position=(gui_x + 68, y + 2), font_size=8, color="black")
97+
98+
# Minus
99+
plotter.add_checkbox_button_widget(lambda b, s=i, c=channel: adjust_rgb(s, c, -5),
100+
position=(gui_x + 85, y), size=18, color_on="white", color_off="white", background_color="white")
101+
plotter.add_text("-", position=(gui_x + 89, y + 2), font_size=8, color="black")
102+
103+
# 2. Create Action Buttons once
104+
y_base = 380
105+
# Tint All
106+
plotter.add_checkbox_button_widget(lambda b: apply_tint_all(), position=(gui_x, y_base), size=40, color_on="lightgrey")
107+
plotter.add_text("Tint ALL", position=(gui_x + 50, y_base + 10), font_size=10, color="black")
108+
# Lighten
109+
plotter.add_checkbox_button_widget(lambda b: adjust_brightness(1.1), position=(gui_x, y_base - 70), size=40, color_on="lightgrey")
110+
plotter.add_text("Lighten All", position=(gui_x + 50, y_base - 60), font_size=10, color="black")
111+
# Darken (Restored)
112+
plotter.add_checkbox_button_widget(lambda b: adjust_brightness(0.9), position=(gui_x, y_base - 140), size=40, color_on="lightgrey")
113+
plotter.add_text("Darken All", position=(gui_x + 50, y_base - 130), font_size=10, color="black")
114+
# Save
115+
plotter.add_checkbox_button_widget(lambda b: save_model(), position=(gui_x, y_base - 210), size=40, color_on="lightgrey")
116+
plotter.add_text("Save", position=(gui_x + 50, y_base - 200), font_size=10, color="black")
117+
118+
# 3. Restriction Toggle
119+
plotter.add_checkbox_button_widget(toggle_restriction, value=restrict_changes, position=(gui_x, 50), size=30)
120+
plotter.add_text("restrict to 1 change each", position=(gui_x + 40, 55), font_size=9, color="black")
121+
122+
# 4. Point Size Slider
123+
plotter.add_slider_widget(update_point_size, [1, 250], value=100, title="",
124+
pointa=(0.6, 0.05), pointb=(0.9, 0.05))
125+
126+
# --- Logic Actions ---
127+
128+
def apply_tint_all():
129+
global current_rgb
130+
tint = colors[active_slot].astype(float) / 255.0
131+
for i in range(color_count):
132+
if restrict_changes and modified_mask[i]: continue
133+
current_rgb[i] = np.clip(original_rgb[i] * tint, 0, 255).astype(np.uint8)
134+
modified_mask[i] = True
135+
refresh_mesh()
136+
137+
def adjust_brightness(factor):
138+
global current_rgb
139+
indices = np.where(modified_mask)[0]
140+
for i in indices:
141+
current_rgb[i] = np.clip(current_rgb[i].astype(float) * factor, 0, 255).astype(np.uint8)
142+
refresh_mesh()
143+
144+
def toggle_restriction(state):
145+
global restrict_changes
146+
restrict_changes = state
147+
148+
def update_point_size(value):
149+
global point_size_val
150+
point_size_val = value
151+
refresh_mesh()
152+
153+
def save_model():
154+
for i in range(color_count):
155+
pos = color_offset + (i * 4)
156+
full_raw_data[pos:pos+3] = current_rgb[i, [2, 1, 0]]
157+
full_raw_data[pos+3] = 255
158+
with open(f'{FILE_NAME}_shaded.p', 'wb') as f:
159+
f.write(full_raw_data)
160+
print("Export successful.")
161+
162+
def on_click(point):
163+
idx = cloud.find_closest_point(point)
164+
if restrict_changes and modified_mask[idx]: return
165+
tint = colors[active_slot].astype(float) / 255.0
166+
current_rgb[idx] = np.clip(original_rgb[idx] * tint, 0, 255).astype(np.uint8)
167+
modified_mask[idx] = True
168+
refresh_mesh()
169+
170+
# --- Execution ---
171+
refresh_mesh()
172+
build_static_ui()
173+
update_color_displays() # Build initial dynamic labels
174+
plotter.enable_point_picking(callback=on_click, show_message=False, color='yellow')
175+
plotter.show()

0 commit comments

Comments
 (0)