1# ##### BEGIN GPL LICENSE BLOCK ##### 2# 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. 7# 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. 12# 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. 16# 17# ##### END GPL LICENSE BLOCK ##### 18 19 20if "bpy" in locals(): 21 from importlib import reload 22 23 utils = reload(utils) 24else: 25 from blenderkit import utils 26 27import bpy 28from object_print3d_utils import operators as ops 29 30RENDER_OBTYPES = ['MESH', 'CURVE', 'SURFACE', 'METABALL', 'TEXT'] 31 32 33def check_material(props, mat): 34 e = bpy.context.scene.render.engine 35 shaders = [] 36 textures = [] 37 props.texture_count = 0 38 props.node_count = 0 39 props.total_megapixels = 0 40 props.is_procedural = True 41 42 if e == 'CYCLES': 43 44 if mat.node_tree is not None: 45 checknodes = mat.node_tree.nodes[:] 46 while len(checknodes) > 0: 47 n = checknodes.pop() 48 props.node_count += 1 49 if n.type == 'GROUP': # dive deeper here. 50 checknodes.extend(n.node_tree.nodes) 51 if len(n.outputs) == 1 and n.outputs[0].type == 'SHADER' and n.type != 'GROUP': 52 if n.type not in shaders: 53 shaders.append(n.type) 54 if n.type == 'TEX_IMAGE': 55 56 if n.image is not None: 57 mattype = 'image based' 58 props.is_procedural = False 59 if n.image not in textures: 60 textures.append(n.image) 61 props.texture_count += 1 62 props.total_megapixels += (n.image.size[0] * n.image.size[1]) 63 64 maxres = max(n.image.size[0], n.image.size[1]) 65 props.texture_resolution_max = max(props.texture_resolution_max, maxres) 66 minres = min(n.image.size[0], n.image.size[1]) 67 if props.texture_resolution_min == 0: 68 props.texture_resolution_min = minres 69 else: 70 props.texture_resolution_min = min(props.texture_resolution_min, minres) 71 72 props.shaders = '' 73 for s in shaders: 74 if s.startswith('BSDF_'): 75 s = s[5:] 76 s = s.lower().replace('_', ' ') 77 props.shaders += (s + ', ') 78 79 80def check_render_engine(props, obs): 81 ob = obs[0] 82 m = None 83 84 e = bpy.context.scene.render.engine 85 mattype = None 86 materials = [] 87 shaders = [] 88 textures = [] 89 props.uv = False 90 props.texture_count = 0 91 props.total_megapixels = 0 92 props.node_count = 0 93 for ob in obs: # TODO , this is duplicated here for other engines, otherwise this should be more clever. 94 for ms in ob.material_slots: 95 if ms.material is not None: 96 m = ms.material 97 if m.name not in materials: 98 materials.append(m.name) 99 if ob.type == 'MESH' and len(ob.data.uv_layers) > 0: 100 props.uv = True 101 102 if e == 'BLENDER_RENDER': 103 props.engine = 'BLENDER_INTERNAL' 104 elif e == 'CYCLES': 105 106 props.engine = 'CYCLES' 107 108 for mname in materials: 109 m = bpy.data.materials[mname] 110 if m is not None and m.node_tree is not None: 111 checknodes = m.node_tree.nodes[:] 112 while len(checknodes) > 0: 113 n = checknodes.pop() 114 props.node_count +=1 115 if n.type == 'GROUP': # dive deeper here. 116 checknodes.extend(n.node_tree.nodes) 117 if len(n.outputs) == 1 and n.outputs[0].type == 'SHADER' and n.type != 'GROUP': 118 if n.type not in shaders: 119 shaders.append(n.type) 120 if n.type == 'TEX_IMAGE': 121 122 123 if n.image is not None and n.image not in textures: 124 props.is_procedural = False 125 mattype = 'image based' 126 127 textures.append(n.image) 128 props.texture_count += 1 129 props.total_megapixels += (n.image.size[0] * n.image.size[1]) 130 131 maxres = max(n.image.size[0], n.image.size[1]) 132 props.texture_resolution_max = max(props.texture_resolution_max, maxres) 133 minres = min(n.image.size[0], n.image.size[1]) 134 if props.texture_resolution_min == 0: 135 props.texture_resolution_min = minres 136 else: 137 props.texture_resolution_min = min(props.texture_resolution_min, minres) 138 139 140 # if mattype == None: 141 # mattype = 'procedural' 142 # tags['material type'] = mattype 143 144 elif e == 'BLENDER_GAME': 145 props.engine = 'BLENDER_GAME' 146 147 # write to object properties. 148 props.materials = '' 149 props.shaders = '' 150 for m in materials: 151 props.materials += (m + ', ') 152 for s in shaders: 153 if s.startswith('BSDF_'): 154 s = s[5:] 155 s = s.lower() 156 s = s.replace('_', ' ') 157 props.shaders += (s + ', ') 158 159 160def check_printable(props, obs): 161 if len(obs) == 1: 162 check_cls = ( 163 ops.Print3DCheckSolid, 164 ops.Print3DCheckIntersections, 165 ops.Print3DCheckDegenerate, 166 ops.Print3DCheckDistorted, 167 ops.Print3DCheckThick, 168 ops.Print3DCheckSharp, 169 # ops.Print3DCheckOverhang, 170 ) 171 172 ob = obs[0] 173 174 info = [] 175 for cls in check_cls: 176 cls.main_check(ob, info) 177 178 printable = True 179 for item in info: 180 passed = item[0].endswith(' 0') 181 if not passed: 182 print(item[0]) 183 printable = False 184 185 props.printable_3d = printable 186 187 188def check_rig(props, obs): 189 for ob in obs: 190 if ob.type == 'ARMATURE': 191 props.rig = True 192 193 194def check_anim(props, obs): 195 animated = False 196 for ob in obs: 197 if ob.animation_data is not None: 198 a = ob.animation_data.action 199 if a is not None: 200 for c in a.fcurves: 201 if len(c.keyframe_points) > 1: 202 animated = True 203 204 # c.keyframe_points.remove(c.keyframe_points[0]) 205 if animated: 206 props.animated = True 207 208 209def check_meshprops(props, obs): 210 ''' checks polycount, manifold, mesh parts (not implemented)''' 211 fc = 0 212 fcr = 0 213 tris = 0 214 quads = 0 215 ngons = 0 216 vc = 0 217 218 edges_counts = {} 219 manifold = True 220 221 for ob in obs: 222 if ob.type == 'MESH' or ob.type == 'CURVE': 223 ob_eval = None 224 if ob.type == 'CURVE': 225 # depsgraph = bpy.context.evaluated_depsgraph_get() 226 # object_eval = ob.evaluated_get(depsgraph) 227 mesh = ob.to_mesh() 228 else: 229 mesh = ob.data 230 fco = len(mesh.polygons) 231 fc += fco 232 vc += len(mesh.vertices) 233 fcor = fco 234 for f in mesh.polygons: 235 # face sides counter 236 if len(f.vertices) == 3: 237 tris += 1 238 elif len(f.vertices) == 4: 239 quads += 1 240 elif len(f.vertices) > 4: 241 ngons += 1 242 243 # manifold counter 244 for i, v in enumerate(f.vertices): 245 v1 = f.vertices[i - 1] 246 e = (min(v, v1), max(v, v1)) 247 edges_counts[e] = edges_counts.get(e, 0) + 1 248 249 # all meshes have to be manifold for this to work. 250 manifold = manifold and not any(i in edges_counts.values() for i in [0, 1, 3, 4]) 251 252 for m in ob.modifiers: 253 if m.type == 'SUBSURF' or m.type == 'MULTIRES': 254 fcor *= 4 ** m.render_levels 255 if m.type == 'SOLIDIFY': # this is rough estimate, not to waste time with evaluating all nonmanifold edges 256 fcor *= 2 257 if m.type == 'ARRAY': 258 fcor *= m.count 259 if m.type == 'MIRROR': 260 fcor *= 2 261 if m.type == 'DECIMATE': 262 fcor *= m.ratio 263 fcr += fcor 264 265 if ob_eval: 266 ob_eval.to_mesh_clear() 267 268 # write out props 269 props.face_count = fc 270 props.face_count_render = fcr 271 # print(tris, quads, ngons) 272 if quads > 0 and tris == 0 and ngons == 0: 273 props.mesh_poly_type = 'QUAD' 274 elif quads > tris and quads > ngons: 275 props.mesh_poly_type = 'QUAD_DOMINANT' 276 elif tris > quads and tris > quads: 277 props.mesh_poly_type = 'TRI_DOMINANT' 278 elif quads == 0 and tris > 0 and ngons == 0: 279 props.mesh_poly_type = 'TRI' 280 elif ngons > quads and ngons > tris: 281 props.mesh_poly_type = 'NGON' 282 else: 283 props.mesh_poly_type = 'OTHER' 284 285 props.manifold = manifold 286 287 288def countObs(props, obs): 289 ob_types = {} 290 count = len(obs) 291 for ob in obs: 292 otype = ob.type.lower() 293 ob_types[otype] = ob_types.get(otype, 0) + 1 294 props.object_count = count 295 296 297def check_modifiers(props, obs): 298 # modif_mapping = { 299 # } 300 modifiers = [] 301 for ob in obs: 302 for m in ob.modifiers: 303 mtype = m.type 304 mtype = mtype.replace('_', ' ') 305 mtype = mtype.lower() 306 # mtype = mtype.capitalize() 307 if mtype not in modifiers: 308 modifiers.append(mtype) 309 if m.type == 'SMOKE': 310 if m.smoke_type == 'FLOW': 311 smt = m.flow_settings.smoke_flow_type 312 if smt == 'BOTH' or smt == 'FIRE': 313 modifiers.append('fire') 314 315 # for mt in modifiers: 316 effectmodifiers = ['soft body', 'fluid simulation', 'particle system', 'collision', 'smoke', 'cloth', 317 'dynamic paint'] 318 for m in modifiers: 319 if m in effectmodifiers: 320 props.simulation = True 321 if ob.rigid_body is not None: 322 props.simulation = True 323 modifiers.append('rigid body') 324 finalstr = '' 325 for m in modifiers: 326 finalstr += m 327 finalstr += ',' 328 props.modifiers = finalstr 329 330 331def get_autotags(): 332 """ call all analysis functions """ 333 ui = bpy.context.scene.blenderkitUI 334 if ui.asset_type == 'MODEL': 335 ob = utils.get_active_model() 336 obs = utils.get_hierarchy(ob) 337 props = ob.blenderkit 338 if props.name == "": 339 props.name = ob.name 340 341 # reset some properties here, because they might not get re-filled at all when they aren't needed anymore. 342 props.texture_resolution_max = 0 343 props.texture_resolution_min = 0 344 # disabled printing checking, some 3d print addon bug. 345 # check_printable( props, obs) 346 check_render_engine(props, obs) 347 348 dim, bbox_min, bbox_max = utils.get_dimensions(obs) 349 props.dimensions = dim 350 props.bbox_min = bbox_min 351 props.bbox_max = bbox_max 352 353 check_rig(props, obs) 354 check_anim(props, obs) 355 check_meshprops(props, obs) 356 check_modifiers(props, obs) 357 countObs(props, obs) 358 elif ui.asset_type == 'MATERIAL': 359 # reset some properties here, because they might not get re-filled at all when they aren't needed anymore. 360 361 mat = utils.get_active_asset() 362 props = mat.blenderkit 363 props.texture_resolution_max = 0 364 props.texture_resolution_min = 0 365 check_material(props, mat) 366 367 368class AutoFillTags(bpy.types.Operator): 369 """Fill tags for asset. Now run before upload, no need to interact from user side.""" 370 bl_idname = "object.blenderkit_auto_tags" 371 bl_label = "Generate Auto Tags for BlenderKit" 372 bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} 373 374 @classmethod 375 def poll(cls, context): 376 return bpy.context.view_layer.objects.active is not None 377 378 def execute(self, context): 379 get_autotags() 380 return {'FINISHED'} 381 382 383def register_asset_inspector(): 384 bpy.utils.register_class(AutoFillTags) 385 386 387def unregister_asset_inspector(): 388 bpy.utils.unregister_class(AutoFillTags) 389 390 391if __name__ == "__main__": 392 register() 393