-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmaterial_checker.py
More file actions
245 lines (211 loc) Β· 8.73 KB
/
Copy pathmaterial_checker.py
File metadata and controls
245 lines (211 loc) Β· 8.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# Blender script: Image-chooser for material Image Texture node
# with pagination (100 per page), 3 columns, large thumbnails (scale=7)
# Paste into Blender Text Editor and Run Script (or install as an add-on).
# Author: ChatGPT (example)
import bpy
import os
import math
import bpy.utils.previews
# ----------------- Configuration -----------------
IMAGE_DIR = bpy.path.abspath("//") # default = folder with .blend
THUMB_EXTS = {".png", ".jpg", ".jpeg", ".tga", ".bmp", ".exr", ".hdr"}
THUMB_COLUMNS = 3 # thumbnails per row (user said keep 3)
THUMB_SCALE = 7 # thumbnail scale (1..10+). you liked 7
PAGE_SIZE = 100 # number of thumbnails per page
# -------------------------------------------------
PREVIEWS = None
IMAGE_FILES = [] # list of (filename, filepath)
def find_image_files(path):
files = []
if not os.path.isdir(path):
return files
for f in sorted(os.listdir(path)):
# Skip hidden/system files starting with '.'
if f.startswith('.'):
continue
ext = os.path.splitext(f)[1].lower()
if ext in THUMB_EXTS:
files.append((f, os.path.join(path, f)))
return files
def ensure_previews():
"""Create previews collection and load previews for all image files found."""
global PREVIEWS, IMAGE_FILES
if PREVIEWS is None:
PREVIEWS = bpy.utils.previews.new()
# re-scan image files if needed
if not IMAGE_FILES:
IMAGE_FILES = find_image_files(IMAGE_DIR)
# load previews for all files (fast enough for a few hundred)
for fname, fpath in IMAGE_FILES:
if PREVIEWS.get(fname) is None:
try:
PREVIEWS.load(fname, fpath, 'IMAGE')
except Exception as e:
print("Preview load failed for", fpath, e)
return PREVIEWS
def clear_previews():
global PREVIEWS
if PREVIEWS is not None:
bpy.utils.previews.remove(PREVIEWS)
PREVIEWS = None
def get_page_info():
"""Return (page_index, page_count) where page_index is 0-based."""
total = len(IMAGE_FILES)
page_count = max(1, math.ceil(total / PAGE_SIZE))
page_index = max(0, min(bpy.context.scene.image_browser_page, page_count - 1))
return page_index, page_count
# Operator: set material image node to the chosen image
class WM_OT_set_material_image(bpy.types.Operator):
bl_idname = "wm.set_material_image"
bl_label = "Set Material Image"
filepath: bpy.props.StringProperty()
filename: bpy.props.StringProperty()
def execute(self, context):
filepath = self.filepath
fname = self.filename
obj = context.active_object
if obj is None:
self.report({'ERROR'}, "No active object")
return {'CANCELLED'}
mat = obj.active_material if obj.active_material else (obj.material_slots[0].material if obj.material_slots else None)
if mat is None:
self.report({'ERROR'}, "Active object has no material")
return {'CANCELLED'}
if not mat.use_nodes:
self.report({'ERROR'}, "Material has no node tree (enable Use Nodes)")
return {'CANCELLED'}
img_node = None
for node in mat.node_tree.nodes:
if node.type == 'TEX_IMAGE':
img_node = node
break
if img_node is None:
self.report({'ERROR'}, "No Image Texture node found in material")
return {'CANCELLED'}
# Load or reuse the image in bpy.data.images
img = bpy.data.images.get(fname)
if img is None:
try:
img = bpy.data.images.load(filepath, check_existing=True)
except Exception as e:
self.report({'ERROR'}, f"Failed to load image: {e}")
return {'CANCELLED'}
img_node.image = img
# ensure viewport updates
for area in context.screen.areas:
if area.type == 'VIEW_3D':
area.tag_redraw()
self.report({'INFO'}, f"Assigned image '{fname}' to material '{mat.name}'")
return {'FINISHED'}
# Pagination operators
class WM_OT_next_page(bpy.types.Operator):
bl_idname = "wm.image_browser_next_page"
bl_label = "Next Image Page"
def execute(self, context):
page_idx, page_count = get_page_info()
if page_idx < page_count - 1:
context.scene.image_browser_page = page_idx + 1
for window in context.window_manager.windows:
for area in window.screen.areas:
area.tag_redraw()
return {'FINISHED'}
class WM_OT_prev_page(bpy.types.Operator):
bl_idname = "wm.image_browser_prev_page"
bl_label = "Previous Image Page"
def execute(self, context):
page_idx, page_count = get_page_info()
if page_idx > 0:
context.scene.image_browser_page = page_idx - 1
for window in context.window_manager.windows:
for area in window.screen.areas:
area.tag_redraw()
return {'FINISHED'}
# Refresh operator (re-scan folder and reset to page 0)
class WM_OT_refresh_image_previews(bpy.types.Operator):
bl_idname = "wm.refresh_image_previews"
bl_label = "Refresh Image Previews"
def execute(self, context):
global IMAGE_FILES
clear_previews()
IMAGE_FILES = find_image_files(IMAGE_DIR)
ensure_previews()
context.scene.image_browser_page = 0
self.report({'INFO'}, f"Found {len(IMAGE_FILES)} images in {IMAGE_DIR}")
return {'FINISHED'}
# Panel: show thumbnails and allow clicking, with pagination
class MATERIAL_PT_image_browser(bpy.types.Panel):
bl_label = "Image Browser (folder)"
bl_idname = "MATERIAL_PT_image_browser"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material"
@classmethod
def poll(cls, context):
return context.active_object is not None and (context.active_object.active_material is not None or len(context.active_object.material_slots) > 0)
def draw(self, context):
layout = self.layout
ensure_previews()
if not IMAGE_FILES:
layout.label(text=f"No images in: {IMAGE_DIR}")
layout.operator("wm.refresh_image_previews", text="Refresh (re-scan folder)")
return
page_idx, page_count = get_page_info()
start = page_idx * PAGE_SIZE
end = start + PAGE_SIZE
page_items = IMAGE_FILES[start:end]
# header with Prev / Page X / Next and refresh button
row = layout.row(align=True)
row.operator("wm.image_browser_prev_page", text="", icon='TRIA_LEFT')
row.label(text=f"Page {page_idx + 1} / {page_count}")
row.operator("wm.image_browser_next_page", text="", icon='TRIA_RIGHT')
row.separator()
row.operator("wm.refresh_image_previews", text="", icon='FILE_REFRESH')
# grid flow for thumbnails
flow = layout.grid_flow(row_major=True, columns=THUMB_COLUMNS, even_columns=True, even_rows=True)
for fname, fpath in page_items:
icon = PREVIEWS.get(fname)
# Make icon button larger using template_icon with scale
col = flow.column()
if icon:
op = col.operator("wm.set_material_image", text="", icon='EYEDROPPER')
#op = col.operator("wm.set_material_image", text="")
op.filepath = fpath
op.filename = fname
# show thumbnail with chosen scale
col.template_icon(icon_value=icon.icon_id, scale=THUMB_SCALE)
else:
# fallback: simple text button
op = col.operator("wm.set_material_image", text=fname)
op.filepath = fpath
op.filename = fname
layout.separator()
layout.label(text=f"Showing {len(page_items)} of {len(IMAGE_FILES)} images (folder: {os.path.basename(IMAGE_DIR)})")
# register/unregister
classes = (
WM_OT_set_material_image,
WM_OT_next_page,
WM_OT_prev_page,
WM_OT_refresh_image_previews,
MATERIAL_PT_image_browser,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
# scene property to hold current page index
bpy.types.Scene.image_browser_page = bpy.props.IntProperty(name="Image Browser Page", default=0, min=0)
# load initial list and previews
global IMAGE_FILES
IMAGE_FILES = find_image_files(IMAGE_DIR)
ensure_previews()
print("Image chooser (paged) registered. Scanned:", IMAGE_DIR)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
try:
del bpy.types.Scene.image_browser_page
except Exception:
pass
clear_previews()
print("Image chooser (paged) unregistered.")
if __name__ == "__main__":
register()