Skip to content

Commit 84e12a4

Browse files
authored
Create PBR-Texture-Generator.py
1 parent a2deba9 commit 84e12a4

1 file changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import tkinter as tk
2+
from tkinter import filedialog, messagebox
3+
import ttkbootstrap as tb
4+
from ttkbootstrap.widgets.scrolled import ScrolledText
5+
import threading
6+
import cv2
7+
import os
8+
import sys
9+
import numpy as np
10+
11+
# =================== Utility ===================
12+
def resource_path(file_name):
13+
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
14+
return os.path.join(base_path, file_name)
15+
16+
# =================== APP ===================
17+
app = tb.Window(
18+
title="PBR Texture Generator — Blending Visuals",
19+
themename="superhero",
20+
size=(1150, 650)
21+
)
22+
23+
app.grid_columnconfigure(0, weight=1)
24+
app.grid_columnconfigure(1, weight=2)
25+
app.grid_rowconfigure(0, weight=1)
26+
27+
# =================== HELPERS ===================
28+
def ui(func, *args):
29+
app.after(0, lambda: func(*args))
30+
31+
def log_line(text):
32+
def _log():
33+
log.text.config(state="normal")
34+
log.text.insert("end", text + "\n")
35+
log.text.see("end")
36+
log.text.config(state="disabled")
37+
ui(_log)
38+
39+
# =================== PANELS ===================
40+
left_panel = tb.Frame(app)
41+
left_panel.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
42+
left_panel.grid_columnconfigure(0, weight=1)
43+
44+
right_panel = tb.Frame(app)
45+
right_panel.grid(row=0, column=1, sticky="nsew", padx=10, pady=10)
46+
right_panel.grid_rowconfigure(0, weight=1)
47+
48+
# =================== INPUT ===================
49+
input_card = tb.Labelframe(left_panel, text="Base Image", padding=15)
50+
input_card.grid(row=0, column=0, sticky="ew", pady=5)
51+
52+
image_path = tk.StringVar()
53+
output_dir = tk.StringVar()
54+
55+
tb.Entry(input_card, textvariable=image_path).grid(row=0, column=0, sticky="ew", padx=5)
56+
tb.Button(
57+
input_card, text="Browse", bootstyle="info",
58+
command=lambda: image_path.set(
59+
filedialog.askopenfilename(filetypes=[("Images", "*.png *.jpg *.jpeg")])
60+
)
61+
).grid(row=0, column=1, padx=5)
62+
63+
input_card.grid_columnconfigure(0, weight=1)
64+
65+
# =================== OUTPUT ===================
66+
out_card = tb.Labelframe(left_panel, text="Output Folder", padding=15)
67+
out_card.grid(row=1, column=0, sticky="ew", pady=5)
68+
69+
tb.Entry(out_card, textvariable=output_dir).grid(row=0, column=0, sticky="ew", padx=5)
70+
tb.Button(
71+
out_card, text="Browse", bootstyle="info",
72+
command=lambda: output_dir.set(filedialog.askdirectory())
73+
).grid(row=0, column=1, padx=5)
74+
75+
out_card.grid_columnconfigure(0, weight=1)
76+
77+
# =================== MAP OPTIONS ===================
78+
map_card = tb.Labelframe(left_panel, text="Maps to Generate", padding=15)
79+
map_card.grid(row=2, column=0, sticky="ew", pady=5)
80+
81+
gen_normal = tk.BooleanVar(value=True)
82+
gen_rough = tk.BooleanVar(value=True)
83+
gen_height = tk.BooleanVar(value=True)
84+
gen_ao = tk.BooleanVar(value=True)
85+
gen_metal = tk.BooleanVar(value=True)
86+
87+
for text, var in [
88+
("Normal Map", gen_normal),
89+
("Roughness Map", gen_rough),
90+
("Height Map", gen_height),
91+
("Ambient Occlusion", gen_ao),
92+
("Metallic Map", gen_metal),
93+
]:
94+
tb.Checkbutton(map_card, text=text, variable=var, bootstyle="success").pack(anchor="w")
95+
96+
# =================== LOG ===================
97+
log_card = tb.Labelframe(right_panel, text="Live Output", padding=15)
98+
log_card.grid(row=0, column=0, sticky="nsew")
99+
log_card.grid_rowconfigure(0, weight=1)
100+
101+
log = ScrolledText(log_card)
102+
log.grid(row=0, column=0, sticky="nsew")
103+
log.text.config(state="disabled")
104+
105+
# =================== PROGRESS ===================
106+
bottom = tb.Frame(app)
107+
bottom.grid(row=1, column=0, columnspan=2, sticky="ew", padx=10, pady=5)
108+
109+
progress = tb.Progressbar(bottom)
110+
progress.grid(row=0, column=0, sticky="ew")
111+
bottom.grid_columnconfigure(0, weight=1)
112+
113+
# =================== PBR GENERATION ===================
114+
def generate_pbr():
115+
if not image_path.get() or not output_dir.get():
116+
messagebox.showerror("Missing Input", "Select image and output folder.")
117+
return
118+
119+
img = cv2.imread(image_path.get())
120+
base = os.path.splitext(os.path.basename(image_path.get()))[0]
121+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
122+
123+
os.makedirs(output_dir.get(), exist_ok=True)
124+
progress.configure(value=0)
125+
126+
steps = sum([
127+
gen_normal.get(), gen_rough.get(),
128+
gen_height.get(), gen_ao.get(),
129+
gen_metal.get()
130+
])
131+
done = 0
132+
133+
# ===== HEIGHT =====
134+
if gen_height.get():
135+
height = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX)
136+
cv2.imwrite(f"{output_dir.get()}/{base}_height.png", height)
137+
log_line("Height map generated")
138+
done += 1
139+
140+
# ===== NORMAL =====
141+
if gen_normal.get():
142+
sobelx = cv2.Sobel(gray, cv2.CV_32F, 1, 0)
143+
sobely = cv2.Sobel(gray, cv2.CV_32F, 0, 1)
144+
normal = np.dstack((-sobelx, -sobely, np.ones_like(gray)))
145+
normal /= np.linalg.norm(normal, axis=2, keepdims=True)
146+
normal = ((normal + 1) * 127.5).astype(np.uint8)
147+
cv2.imwrite(f"{output_dir.get()}/{base}_normal.png", normal)
148+
log_line("Normal map generated")
149+
done += 1
150+
151+
# ===== AO =====
152+
if gen_ao.get():
153+
ao = cv2.GaussianBlur(255 - gray, (15, 15), 0)
154+
cv2.imwrite(f"{output_dir.get()}/{base}_ao.png", ao)
155+
log_line("Ambient Occlusion map generated")
156+
done += 1
157+
158+
# ===== ROUGHNESS =====
159+
if gen_rough.get():
160+
edges = cv2.Canny(gray, 50, 150)
161+
rough = cv2.GaussianBlur(255 - edges, (7, 7), 0)
162+
cv2.imwrite(f"{output_dir.get()}/{base}_roughness.png", rough)
163+
log_line("Roughness map generated")
164+
done += 1
165+
166+
# ===== METALLIC =====
167+
if gen_metal.get():
168+
_, metal = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY)
169+
cv2.imwrite(f"{output_dir.get()}/{base}_metallic.png", metal)
170+
log_line("Metallic map generated")
171+
done += 1
172+
173+
progress.configure(value=100)
174+
messagebox.showinfo("Done", "PBR textures generated successfully.")
175+
176+
# =================== BUTTONS ===================
177+
tb.Button(
178+
bottom,
179+
text="Generate PBR Maps",
180+
bootstyle="success",
181+
width=25,
182+
command=lambda: threading.Thread(target=generate_pbr, daemon=True).start()
183+
).grid(row=1, column=0, pady=5)
184+
185+
app.mainloop()

0 commit comments

Comments
 (0)