Merge branch 'blender-v2.92-release'
[blender-addons.git] / blenderkit / asset_inspector.py
blobe12aa05868da4e61878e113d71b0f449eadd82d8
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
20 from blenderkit import utils
22 import bpy
23 from object_print3d_utils import operators as ops
25 RENDER_OBTYPES = ['MESH', 'CURVE', 'SURFACE', 'METABALL', 'TEXT']
28 def check_material(props, mat):
29 e = bpy.context.scene.render.engine
30 shaders = []
31 textures = []
32 props.texture_count = 0
33 props.node_count = 0
34 props.total_megapixels = 0
35 props.is_procedural = True
37 if e == 'CYCLES':
39 if mat.node_tree is not None:
40 checknodes = mat.node_tree.nodes[:]
41 while len(checknodes) > 0:
42 n = checknodes.pop()
43 props.node_count += 1
44 if n.type == 'GROUP': # dive deeper here.
45 checknodes.extend(n.node_tree.nodes)
46 if len(n.outputs) == 1 and n.outputs[0].type == 'SHADER' and n.type != 'GROUP':
47 if n.type not in shaders:
48 shaders.append(n.type)
49 if n.type == 'TEX_IMAGE':
51 if n.image is not None:
52 mattype = 'image based'
53 props.is_procedural = False
54 if n.image not in textures:
55 textures.append(n.image)
56 props.texture_count += 1
57 props.total_megapixels += (n.image.size[0] * n.image.size[1])
59 maxres = max(n.image.size[0], n.image.size[1])
60 props.texture_resolution_max = max(props.texture_resolution_max, maxres)
61 minres = min(n.image.size[0], n.image.size[1])
62 if props.texture_resolution_min == 0:
63 props.texture_resolution_min = minres
64 else:
65 props.texture_resolution_min = min(props.texture_resolution_min, minres)
67 props.shaders = ''
68 for s in shaders:
69 if s.startswith('BSDF_'):
70 s = s[5:]
71 s = s.lower().replace('_', ' ')
72 props.shaders += (s + ', ')
75 def check_render_engine(props, obs):
76 ob = obs[0]
77 m = None
79 e = bpy.context.scene.render.engine
80 mattype = None
81 materials = []
82 shaders = []
83 textures = []
84 props.uv = False
85 props.texture_count = 0
86 props.total_megapixels = 0
87 props.node_count = 0
88 for ob in obs: # TODO , this is duplicated here for other engines, otherwise this should be more clever.
89 for ms in ob.material_slots:
90 if ms.material is not None:
91 m = ms.material
92 if m.name not in materials:
93 materials.append(m.name)
94 if ob.type == 'MESH' and len(ob.data.uv_layers) > 0:
95 props.uv = True
97 if e == 'BLENDER_RENDER':
98 props.engine = 'BLENDER_INTERNAL'
99 elif e == 'CYCLES':
101 props.engine = 'CYCLES'
103 for mname in materials:
104 m = bpy.data.materials[mname]
105 if m is not None and m.node_tree is not None:
106 checknodes = m.node_tree.nodes[:]
107 while len(checknodes) > 0:
108 n = checknodes.pop()
109 props.node_count +=1
110 if n.type == 'GROUP': # dive deeper here.
111 checknodes.extend(n.node_tree.nodes)
112 if len(n.outputs) == 1 and n.outputs[0].type == 'SHADER' and n.type != 'GROUP':
113 if n.type not in shaders:
114 shaders.append(n.type)
115 if n.type == 'TEX_IMAGE':
118 if n.image is not None and n.image not in textures:
119 props.is_procedural = False
120 mattype = 'image based'
122 textures.append(n.image)
123 props.texture_count += 1
124 props.total_megapixels += (n.image.size[0] * n.image.size[1])
126 maxres = max(n.image.size[0], n.image.size[1])
127 props.texture_resolution_max = max(props.texture_resolution_max, maxres)
128 minres = min(n.image.size[0], n.image.size[1])
129 if props.texture_resolution_min == 0:
130 props.texture_resolution_min = minres
131 else:
132 props.texture_resolution_min = min(props.texture_resolution_min, minres)
135 # if mattype == None:
136 # mattype = 'procedural'
137 # tags['material type'] = mattype
139 elif e == 'BLENDER_GAME':
140 props.engine = 'BLENDER_GAME'
142 # write to object properties.
143 props.materials = ''
144 props.shaders = ''
145 for m in materials:
146 props.materials += (m + ', ')
147 for s in shaders:
148 if s.startswith('BSDF_'):
149 s = s[5:]
150 s = s.lower()
151 s = s.replace('_', ' ')
152 props.shaders += (s + ', ')
155 def check_printable(props, obs):
156 if len(obs) == 1:
157 check_cls = (
158 ops.Print3DCheckSolid,
159 ops.Print3DCheckIntersections,
160 ops.Print3DCheckDegenerate,
161 ops.Print3DCheckDistorted,
162 ops.Print3DCheckThick,
163 ops.Print3DCheckSharp,
164 # ops.Print3DCheckOverhang,
167 ob = obs[0]
169 info = []
170 for cls in check_cls:
171 cls.main_check(ob, info)
173 printable = True
174 for item in info:
175 passed = item[0].endswith(' 0')
176 if not passed:
177 print(item[0])
178 printable = False
180 props.printable_3d = printable
183 def check_rig(props, obs):
184 for ob in obs:
185 if ob.type == 'ARMATURE':
186 props.rig = True
189 def check_anim(props, obs):
190 animated = False
191 for ob in obs:
192 if ob.animation_data is not None:
193 a = ob.animation_data.action
194 if a is not None:
195 for c in a.fcurves:
196 if len(c.keyframe_points) > 1:
197 animated = True
199 # c.keyframe_points.remove(c.keyframe_points[0])
200 if animated:
201 props.animated = True
204 def check_meshprops(props, obs):
205 ''' checks polycount, manifold, mesh parts (not implemented)'''
206 fc = 0
207 fcr = 0
208 tris = 0
209 quads = 0
210 ngons = 0
211 vc = 0
213 edges_counts = {}
214 manifold = True
216 for ob in obs:
217 if ob.type == 'MESH' or ob.type == 'CURVE':
218 ob_eval = None
219 if ob.type == 'CURVE':
220 # depsgraph = bpy.context.evaluated_depsgraph_get()
221 # object_eval = ob.evaluated_get(depsgraph)
222 mesh = ob.to_mesh()
223 else:
224 mesh = ob.data
225 fco = len(mesh.polygons)
226 fc += fco
227 vc += len(mesh.vertices)
228 fcor = fco
229 for f in mesh.polygons:
230 # face sides counter
231 if len(f.vertices) == 3:
232 tris += 1
233 elif len(f.vertices) == 4:
234 quads += 1
235 elif len(f.vertices) > 4:
236 ngons += 1
238 # manifold counter
239 for i, v in enumerate(f.vertices):
240 v1 = f.vertices[i - 1]
241 e = (min(v, v1), max(v, v1))
242 edges_counts[e] = edges_counts.get(e, 0) + 1
244 # all meshes have to be manifold for this to work.
245 manifold = manifold and not any(i in edges_counts.values() for i in [0, 1, 3, 4])
247 for m in ob.modifiers:
248 if m.type == 'SUBSURF' or m.type == 'MULTIRES':
249 fcor *= 4 ** m.render_levels
250 if m.type == 'SOLIDIFY': # this is rough estimate, not to waste time with evaluating all nonmanifold edges
251 fcor *= 2
252 if m.type == 'ARRAY':
253 fcor *= m.count
254 if m.type == 'MIRROR':
255 fcor *= 2
256 if m.type == 'DECIMATE':
257 fcor *= m.ratio
258 fcr += fcor
260 if ob_eval:
261 ob_eval.to_mesh_clear()
263 # write out props
264 props.face_count = fc
265 props.face_count_render = fcr
266 # print(tris, quads, ngons)
267 if quads > 0 and tris == 0 and ngons == 0:
268 props.mesh_poly_type = 'QUAD'
269 elif quads > tris and quads > ngons:
270 props.mesh_poly_type = 'QUAD_DOMINANT'
271 elif tris > quads and tris > quads:
272 props.mesh_poly_type = 'TRI_DOMINANT'
273 elif quads == 0 and tris > 0 and ngons == 0:
274 props.mesh_poly_type = 'TRI'
275 elif ngons > quads and ngons > tris:
276 props.mesh_poly_type = 'NGON'
277 else:
278 props.mesh_poly_type = 'OTHER'
280 props.manifold = manifold
283 def countObs(props, obs):
284 ob_types = {}
285 count = len(obs)
286 for ob in obs:
287 otype = ob.type.lower()
288 ob_types[otype] = ob_types.get(otype, 0) + 1
289 props.object_count = count
292 def check_modifiers(props, obs):
293 # modif_mapping = {
295 modifiers = []
296 for ob in obs:
297 for m in ob.modifiers:
298 mtype = m.type
299 mtype = mtype.replace('_', ' ')
300 mtype = mtype.lower()
301 # mtype = mtype.capitalize()
302 if mtype not in modifiers:
303 modifiers.append(mtype)
304 if m.type == 'SMOKE':
305 if m.smoke_type == 'FLOW':
306 smt = m.flow_settings.smoke_flow_type
307 if smt == 'BOTH' or smt == 'FIRE':
308 modifiers.append('fire')
310 # for mt in modifiers:
311 effectmodifiers = ['soft body', 'fluid simulation', 'particle system', 'collision', 'smoke', 'cloth',
312 'dynamic paint']
313 for m in modifiers:
314 if m in effectmodifiers:
315 props.simulation = True
316 if ob.rigid_body is not None:
317 props.simulation = True
318 modifiers.append('rigid body')
319 finalstr = ''
320 for m in modifiers:
321 finalstr += m
322 finalstr += ','
323 props.modifiers = finalstr
326 def get_autotags():
327 """ call all analysis functions """
328 ui = bpy.context.scene.blenderkitUI
329 if ui.asset_type == 'MODEL':
330 ob = utils.get_active_model()
331 obs = utils.get_hierarchy(ob)
332 props = ob.blenderkit
333 if props.name == "":
334 props.name = ob.name
336 # reset some properties here, because they might not get re-filled at all when they aren't needed anymore.
337 props.texture_resolution_max = 0
338 props.texture_resolution_min = 0
339 # disabled printing checking, some 3d print addon bug.
340 # check_printable( props, obs)
341 check_render_engine(props, obs)
343 dim, bbox_min, bbox_max = utils.get_dimensions(obs)
344 props.dimensions = dim
345 props.bbox_min = bbox_min
346 props.bbox_max = bbox_max
348 check_rig(props, obs)
349 check_anim(props, obs)
350 check_meshprops(props, obs)
351 check_modifiers(props, obs)
352 countObs(props, obs)
353 elif ui.asset_type == 'MATERIAL':
354 # reset some properties here, because they might not get re-filled at all when they aren't needed anymore.
356 mat = utils.get_active_asset()
357 props = mat.blenderkit
358 props.texture_resolution_max = 0
359 props.texture_resolution_min = 0
360 check_material(props, mat)
361 elif ui.asset_type == 'HDR':
362 # reset some properties here, because they might not get re-filled at all when they aren't needed anymore.
364 hdr = utils.get_active_asset()
365 props = hdr.blenderkit
366 props.texture_resolution_max = max(hdr.size[0],hdr.size[1])
369 class AutoFillTags(bpy.types.Operator):
370 """Fill tags for asset. Now run before upload, no need to interact from user side."""
371 bl_idname = "object.blenderkit_auto_tags"
372 bl_label = "Generate Auto Tags for BlenderKit"
373 bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
375 @classmethod
376 def poll(cls, context):
377 return utils.uploadable_asset_poll()
379 def execute(self, context):
380 get_autotags()
381 return {'FINISHED'}
384 def register_asset_inspector():
385 bpy.utils.register_class(AutoFillTags)
388 def unregister_asset_inspector():
389 bpy.utils.unregister_class(AutoFillTags)
392 if __name__ == "__main__":
393 register()