Category: copy/paste UVs
[blender-addons-contrib.git] / materials_cycles_converter.py
1 # convert_materials_to_cycles.py
2
3 # Copyright (C) 5-mar-2012, Silvio Falcinelli. Fixes by others.
4 #
5 # special thanks to user blenderartists.org cmomoney
6 #
7 # ***** BEGIN GPL LICENSE BLOCK *****
8 #
9 # This program is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU General Public License
11 # as published by the Free Software Foundation; either version 2
12 # of the License, or (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software Foundation,
21 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #
23 # ***** END GPL LICENCE BLOCK *****
24
25 bl_info = {
26     "name": "Convert Materials to Cycles",
27     "author": "Silvio Falcinelli, updates by community",
28     "version": (0, 11, 1),
29     "blender": (2, 71, 0),
30     "location": "Properties > Material > Convert to Cycles",
31     "description": "Convert non-nodes materials to Cycles",
32     "warning": "beta",
33     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Material/Blender_Cycles_Materials_Converter",
34     "category": "Material"}
35
36
37 import bpy
38 import math
39 from math import log
40 from math import pow
41 from math import exp
42 import os.path
43
44 def AutoNodeOff():
45     mats = bpy.data.materials
46     for cmat in mats:
47         cmat.use_nodes = False
48     bpy.context.scene.render.engine = 'BLENDER_RENDER'
49
50 def BakingText(tex, mode):
51     print('________________________________________')
52     print('INFO start bake texture ' + tex.name)
53     bpy.ops.object.mode_set(mode='OBJECT')
54     sc = bpy.context.scene
55     tmat = ''
56     img = ''
57     Robj = bpy.context.active_object
58     for n in bpy.data.materials:
59         if n.name == 'TMP_BAKING':
60             tmat = n
61     if not tmat:
62         tmat = bpy.data.materials.new('TMP_BAKING')
63         tmat.name = "TMP_BAKING"
64
65     bpy.ops.mesh.primitive_plane_add()
66     tm = bpy.context.active_object
67     tm.name = "TMP_BAKING"
68     tm.data.name = "TMP_BAKING"
69     bpy.ops.object.select_pattern(extend=False, pattern="TMP_BAKING", case_sensitive=False)
70     sc.objects.active = tm
71     bpy.context.scene.render.engine = 'BLENDER_RENDER'
72     tm.data.materials.append(tmat)
73     if len(tmat.texture_slots.items()) == 0:
74         tmat.texture_slots.add()
75     tmat.texture_slots[0].texture_coords = 'UV'
76     tmat.texture_slots[0].use_map_alpha = True
77     tmat.texture_slots[0].texture = tex.texture
78     tmat.texture_slots[0].use_map_alpha = True
79     tmat.texture_slots[0].use_map_color_diffuse = False
80     tmat.use_transparency = True
81     tmat.alpha = 0
82     tmat.use_nodes = False
83     tmat.diffuse_color = 1, 1, 1
84     bpy.ops.object.mode_set(mode='EDIT')
85     bpy.ops.uv.unwrap()
86
87     for n in bpy.data.images:
88         if n.name == 'TMP_BAKING':
89             n.user_clear()
90             bpy.data.images.remove(n)
91
92     if mode == "ALPHA" and tex.texture.type == 'IMAGE':
93         sizeX = tex.texture.image.size[0]
94         sizeY = tex.texture.image.size[1]
95     else:
96         sizeX = 600
97         sizeY = 600
98     bpy.ops.image.new(name="TMP_BAKING", width=sizeX, height=sizeY, color=(0.0, 0.0, 0.0, 1.0), alpha=True, float=False)
99     bpy.data.screens['UV Editing'].areas[1].spaces[0].image = bpy.data.images["TMP_BAKING"]
100     sc.render.engine = 'BLENDER_RENDER'
101     img = bpy.data.images["TMP_BAKING"]
102     img = bpy.data.images.get("TMP_BAKING")
103     img.file_format = "JPEG"
104     if mode == "ALPHA" and tex.texture.type == 'IMAGE':
105         img.filepath_raw = tex.texture.image.filepath + "_BAKING.jpg"
106
107     else:
108         img.filepath_raw = tex.texture.name + "_PTEXT.jpg"
109
110     sc.render.bake_type = 'ALPHA'
111     sc.render.use_bake_selected_to_active = True
112     sc.render.use_bake_clear = True
113     bpy.ops.object.bake_image()
114     img.save()
115     bpy.ops.object.mode_set(mode='OBJECT')
116     bpy.ops.object.delete()
117     bpy.ops.object.select_pattern(extend=False, pattern=Robj.name, case_sensitive=False)
118     sc.objects.active = Robj
119     img.user_clear()
120     bpy.data.images.remove(img)
121     bpy.data.materials.remove(tmat)
122
123     # print('INFO : end Bake ' + img.filepath_raw )
124     print('________________________________________')
125
126 def AutoNode(active=False):
127     
128     sc = bpy.context.scene
129
130     if active:
131
132          mats = bpy.context.active_object.data.materials
133
134     else:
135
136         mats = bpy.data.materials
137     
138
139     for cmat in mats:
140         cmat.use_nodes = True
141         TreeNodes = cmat.node_tree
142         links = TreeNodes.links
143
144         # Don't alter nodes of locked materials
145         locked = False
146         for n in TreeNodes.nodes:
147             if n.type == 'ShaderNodeOutputMaterial':
148                 if n.label == 'Locked':
149                     locked = True
150                     break
151
152         if not locked:
153             # Convert this material from non-nodes to Cycles nodes
154
155             shader = ''
156             shmix = ''
157             shtsl = ''
158             Add_Emission = ''
159             Add_Translucent = ''
160             Mix_Alpha = ''
161             sT = False
162             
163             for n in TreeNodes.nodes:
164                 TreeNodes.nodes.remove(n)
165
166             # Starting point is diffuse BSDF and output material
167             shader = TreeNodes.nodes.new('ShaderNodeBsdfDiffuse')
168             shader.location = 0, 470
169             shout = TreeNodes.nodes.new('ShaderNodeOutputMaterial')
170             shout.location = 200, 400
171             links.new(shader.outputs[0], shout.inputs[0])
172
173             sM = True
174             for tex in cmat.texture_slots:
175                 if tex:
176                     if tex.use:
177                         if tex.use_map_alpha:
178                             sM = False
179                             if sc.EXTRACT_ALPHA:
180                                 if tex.texture.type == 'IMAGE' and tex.texture.use_alpha:
181                                     if not os.path.exists(bpy.path.abspath(tex.texture.image.filepath + "_BAKING.jpg")) or sc.EXTRACT_OW:
182                                         BakingText(tex, 'ALPHA')
183                                 else:
184                                     if not tex.texture.type == 'IMAGE':
185                                         if not os.path.exists(bpy.path.abspath(tex.texture.name + "_PTEXT.jpg")) or sc.EXTRACT_OW:
186                                             BakingText(tex, 'PTEXT')
187
188             cmat_is_transp = cmat.use_transparency and cmat.alpha < 1
189
190             if cmat_is_transp and cmat.raytrace_transparency.ior == 1 and not cmat.raytrace_mirror.use  and sM:
191                 if not shader.type == 'ShaderNodeBsdfTransparent':
192                     print("INFO:  Make TRANSPARENT shader node " + cmat.name)
193                     TreeNodes.nodes.remove(shader)
194                     shader = TreeNodes.nodes.new('ShaderNodeBsdfTransparent')
195                     shader.location = 0, 470
196                     links.new(shader.outputs[0], shout.inputs[0])
197
198             if not cmat.raytrace_mirror.use and not cmat_is_transp:
199                 if not shader.type == 'ShaderNodeBsdfDiffuse':
200                     print("INFO:  Make DIFFUSE shader node" + cmat.name)
201                     TreeNodes.nodes.remove(shader)
202                     shader = TreeNodes.nodes.new('ShaderNodeBsdfDiffuse')
203                     shader.location = 0, 470
204                     links.new(shader.outputs[0], shout.inputs[0])
205
206             if cmat.raytrace_mirror.use and cmat.raytrace_mirror.reflect_factor > 0.001 and cmat_is_transp:
207                 if not shader.type == 'ShaderNodeBsdfGlass':
208                     print("INFO:  Make GLASS shader node" + cmat.name)
209                     TreeNodes.nodes.remove(shader)
210                     shader = TreeNodes.nodes.new('ShaderNodeBsdfGlass')
211                     shader.location = 0, 470
212                     links.new(shader.outputs[0], shout.inputs[0])
213
214             if cmat.raytrace_mirror.use and not cmat_is_transp and cmat.raytrace_mirror.reflect_factor > 0.001 :
215                 if not shader.type == 'ShaderNodeBsdfGlossy':
216                     print("INFO:  Make MIRROR shader node" + cmat.name)
217                     TreeNodes.nodes.remove(shader)
218                     shader = TreeNodes.nodes.new('ShaderNodeBsdfGlossy')
219                     shader.location = 0, 520
220                     links.new(shader.outputs[0], shout.inputs[0])
221
222             if cmat.emit > 0.001 :
223                 if not shader.type == 'ShaderNodeEmission' and not cmat.raytrace_mirror.reflect_factor > 0.001 and not cmat_is_transp:
224                     print("INFO:  Mix EMISSION shader node" + cmat.name)
225                     TreeNodes.nodes.remove(shader)
226                     shader = TreeNodes.nodes.new('ShaderNodeEmission')
227                     shader.location = 0, 450
228                     links.new(shader.outputs[0], shout.inputs[0])
229                 else:
230                     if not Add_Emission:
231                         print("INFO:  Add EMISSION shader node" + cmat.name)
232                         shout.location = 550, 330
233                         Add_Emission = TreeNodes.nodes.new('ShaderNodeAddShader')
234                         Add_Emission.location = 370, 490
235
236                         shem = TreeNodes.nodes.new('ShaderNodeEmission')
237                         shem.location = 180, 380
238
239                         links.new(Add_Emission.outputs[0], shout.inputs[0])
240                         links.new(shem.outputs[0], Add_Emission.inputs[1])
241                         links.new(shader.outputs[0], Add_Emission.inputs[0])
242
243                         shem.inputs['Color'].default_value = cmat.diffuse_color.r, cmat.diffuse_color.g, cmat.diffuse_color.b, 1
244                         shem.inputs['Strength'].default_value = cmat.emit
245
246             if cmat.translucency > 0.001 :
247                 print("INFO:  Add BSDF_TRANSLUCENT shader node" + cmat.name)
248                 shout.location = 770, 330
249                 Add_Translucent = TreeNodes.nodes.new('ShaderNodeAddShader')
250                 Add_Translucent.location = 580, 490
251
252                 shtsl = TreeNodes.nodes.new('ShaderNodeBsdfTranslucent')
253                 shtsl.location = 400, 350
254
255                 links.new(Add_Translucent.outputs[0], shout.inputs[0])
256                 links.new(shtsl.outputs[0], Add_Translucent.inputs[1])
257
258                 if Add_Emission:
259                     links.new(Add_Emission.outputs[0], Add_Translucent.inputs[0])
260                     pass
261                 else:
262                     links.new(shader.outputs[0], Add_Translucent.inputs[0])
263                     pass
264                 shtsl.inputs['Color'].default_value = cmat.translucency, cmat.translucency, cmat.translucency, 1
265
266             shader.inputs['Color'].default_value = cmat.diffuse_color.r, cmat.diffuse_color.g, cmat.diffuse_color.b, 1
267
268             if shader.type == 'ShaderNodeBsdfDiffuse':
269                 shader.inputs['Roughness'].default_value = cmat.specular_intensity
270
271             if shader.type == 'ShaderNodeBsdfGlossy':
272                 shader.inputs['Roughness'].default_value = 1 - cmat.raytrace_mirror.gloss_factor
273
274             if shader.type == 'ShaderNodeBsdfGlass':
275                 shader.inputs['Roughness'].default_value = 1 - cmat.raytrace_mirror.gloss_factor
276                 shader.inputs['IOR'].default_value = cmat.raytrace_transparency.ior
277
278             if shader.type == 'ShaderNodeEmission':
279                 shader.inputs['Strength'].default_value = cmat.emit
280
281             for tex in cmat.texture_slots:
282                 sT = False
283                 pText = ''
284                 if tex:
285                     if tex.use:
286                         if tex.texture.type == 'IMAGE':
287                             img = tex.texture.image
288                             shtext = TreeNodes.nodes.new('ShaderNodeTexImage')
289                             shtext.location = -200, 400
290                             shtext.image = img
291                             sT = True
292
293                         if not tex.texture.type == 'IMAGE':
294                             if sc.EXTRACT_PTEX:
295                                 print('INFO : Extract Procedural Texture  ')
296                                 if not os.path.exists(bpy.path.abspath(tex.texture.name + "_PTEXT.jpg")) or sc.EXTRACT_OW:
297                                     BakingText(tex, 'PTEX')
298
299                                 img = bpy.data.images.load(tex.texture.name + "_PTEXT.jpg")
300                                 shtext = TreeNodes.nodes.new('ShaderNodeTexImage')
301                                 shtext.location = -200, 400
302                                 shtext.image = img
303                                 sT = True
304                 if sT:
305                         if tex.use_map_color_diffuse :
306                             links.new(shtext.outputs[0], shader.inputs[0])
307
308                         if tex.use_map_emit:
309                             if not Add_Emission:
310                                 print("INFO:  Mix EMISSION + Texture shader node " + cmat.name)
311
312                                 intensity = 0.5 + (tex.emit_factor / 2)
313
314                                 shout.location = 550, 330
315                                 Add_Emission = TreeNodes.nodes.new('ShaderNodeAddShader')
316                                 Add_Emission.name = "Add_Emission"
317                                 Add_Emission.location = 370, 490
318
319                                 shem = TreeNodes.nodes.new('ShaderNodeEmission')
320                                 shem.location = 180, 380
321
322                                 links.new(Add_Emission.outputs[0], shout.inputs[0])
323                                 links.new(shem.outputs[0], Add_Emission.inputs[1])
324                                 links.new(shader.outputs[0], Add_Emission.inputs[0])
325
326                                 shem.inputs['Color'].default_value = cmat.diffuse_color.r, cmat.diffuse_color.g, cmat.diffuse_color.b, 1
327                                 shem.inputs['Strength'].default_value = intensity * 2
328
329                             links.new(shtext.outputs[0], shem.inputs[0])
330
331                         if tex.use_map_mirror:
332                             links.new(shader.inputs[0], shtext.outputs[0])
333
334                         if tex.use_map_translucency:
335                             if not Add_Translucent:
336                                 print("INFO:  Add Translucency + Texture shader node " + cmat.name)
337
338                                 intensity = 0.5 + (tex.emit_factor / 2)
339
340                                 shout.location = 550, 330
341                                 Add_Translucent = TreeNodes.nodes.new('ShaderNodeAddShader')
342                                 Add_Translucent.name = "Add_Translucent"
343                                 Add_Translucent.location = 370, 290
344
345                                 shtsl = TreeNodes.nodes.new('ShaderNodeBsdfTranslucent')
346                                 shtsl.location = 180, 240
347
348                                 links.new(shtsl.outputs[0], Add_Translucent.inputs[1])
349
350                                 if Add_Emission:
351                                     links.new(Add_Translucent.outputs[0], shout.inputs[0])
352                                     links.new(Add_Emission.outputs[0], Add_Translucent.inputs[0])
353                                     pass
354                                 else:
355                                     links.new(Add_Translucent.outputs[0], shout.inputs[0])
356                                     links.new(shader.outputs[0], Add_Translucent.inputs[0])
357
358                             links.new(shtext.outputs[0], shtsl.inputs[0])
359
360                         if tex.use_map_alpha:
361                             if not Mix_Alpha:
362                                 print("INFO:  Mix Alpha + Texture shader node " + cmat.name)
363
364                                 shout.location = 750, 330
365                                 Mix_Alpha = TreeNodes.nodes.new('ShaderNodeMixShader')
366                                 Mix_Alpha.name = "Add_Alpha"
367                                 Mix_Alpha.location = 570, 290
368                                 sMask = TreeNodes.nodes.new('ShaderNodeBsdfTransparent')
369                                 sMask.location = 250, 180
370                                 tMask = TreeNodes.nodes.new('ShaderNodeTexImage')
371                                 tMask.location = -200, 250
372
373                                 if tex.texture.type == 'IMAGE':
374                                     # if tex.texture.use_alpha:
375                                     #    imask=bpy.data.images.load(img.filepath+"_BAKING.jpg")
376                                     # else:
377                                     imask = bpy.data.images.load(img.filepath)
378                                 else:
379                                     imask = bpy.data.images.load(img.name)
380
381                                 tMask.image = imask
382                                 links.new(Mix_Alpha.inputs[0], tMask.outputs[1])
383                                 links.new(shout.inputs[0], Mix_Alpha.outputs[0])
384                                 links.new(sMask.outputs[0], Mix_Alpha.inputs[1])
385
386                                 if not Add_Emission and not Add_Translucent:
387                                     links.new(Mix_Alpha.inputs[2], shader.outputs[0])
388
389                                 if Add_Emission and not Add_Translucent:
390                                     links.new(Mix_Alpha.inputs[2], Add_Emission.outputs[0])
391
392                                 if Add_Translucent:
393                                     links.new(Mix_Alpha.inputs[2], Add_Translucent.outputs[0])
394
395                         if tex.use_map_normal:
396                             t = TreeNodes.nodes.new('ShaderNodeRGBToBW')
397                             t.location = -0, 300
398                             links.new(t.outputs[0], shout.inputs[2])
399                             links.new(shtext.outputs[0], t.inputs[0])
400     bpy.context.scene.render.engine = 'CYCLES'
401
402 class mllock(bpy.types.Operator):
403     bl_idname = "ml.lock"
404     bl_label = "Lock"
405     bl_description = "Lock/unlock this material against modification by conversions"
406     bl_register = True
407     bl_undo = True
408
409     @classmethod
410     def poll(cls, context):
411         return True
412
413     def execute(self, context):
414         cmat = bpy.context.selected_objects[0].active_material
415         TreeNodes = cmat.node_tree
416         for n in TreeNodes.nodes:
417             if n.type == 'ShaderNodeOutputMaterial':
418                 if n.label == 'Locked':
419                     n.label = ''
420                 else:
421                     n.label = 'Locked'
422         return {'FINISHED'}
423
424 class mlrefresh(bpy.types.Operator):
425     bl_idname = "ml.refresh"
426     bl_label = "Convert All Materials"
427     bl_description = "Convert all materials in the scene from non-nodes to Cycles"
428     bl_register = True
429     bl_undo = True
430
431     @classmethod
432     def poll(cls, context):
433         return True
434     
435     def execute(self, context):
436         AutoNode()
437         return {'FINISHED'}
438
439 class mlrefresh_active(bpy.types.Operator):
440     bl_idname = "ml.refresh_active"
441     bl_label = "Convert All Materials From Active Object"
442     bl_description = "Convert all materials from actice object from non-nodes to Cycles"
443     bl_register = True
444     bl_undo = True
445
446     @classmethod
447     def poll(cls, context):
448         return True
449     
450     def execute(self, context):
451         AutoNode(True)
452         return {'FINISHED'}
453
454 class mlrestore(bpy.types.Operator):
455     bl_idname = "ml.restore"
456     bl_label = "Restore"
457     bl_description = "Switch Back to non nodes & Blender Internal"
458     bl_register = True
459     bl_undo = True
460     @classmethod
461     def poll(cls, context):
462         return True
463     def execute(self, context):
464         AutoNodeOff()
465         return {'FINISHED'}
466
467 from bpy.props import *
468 sc = bpy.types.Scene
469 sc.EXTRACT_ALPHA = BoolProperty(attr="EXTRACT_ALPHA", default=False)
470 sc.EXTRACT_PTEX = BoolProperty(attr="EXTRACT_PTEX", default=False)
471 sc.EXTRACT_OW = BoolProperty(attr="Overwrite", default=False, description="Extract textures again instead of re-using priorly extracted textures")
472
473
474 class OBJECT_PT_scenemassive(bpy.types.Panel):
475     bl_label = "Convert Materials to Cycles"
476     bl_space_type = "PROPERTIES"
477     bl_region_type = "WINDOW"
478     bl_context = "material"
479
480     def draw(self, context):
481         sc = context.scene
482         layout = self.layout
483         row = layout.row()
484         box = row.box()
485         box.operator("ml.refresh", text='Convert All to Cycles', icon='TEXTURE')
486         box.operator("ml.refresh_active", text='Convert Active to Cycles', icon='TEXTURE')
487 #        box.prop(sc, "EXTRACT_ALPHA", text='Extract Alpha Textures (slow)')
488 #        box.prop(sc, "EXTRACT_PTEX", text='Extract Procedural Textures (slow)')
489 #        box.prop(sc, "EXTRACT_OW", text='Re-extract Textures')
490         row = layout.row()
491         row.operator("ml.restore", text='Back to Blender', icon='MATERIAL')
492
493         # Locking of nodes objects
494 '''        try:
495             cmat = bpy.context.selected_objects[0].active_material
496             TreeNodes = cmat.node_tree # throws exception for non-nodes mats
497             locked = False
498             for n in TreeNodes.nodes: # throws exception if no node mat
499                 if n.type == 'ShaderNodeOutputMaterial':
500                     if n.label == 'Locked':
501                         locked = True
502                         break
503             
504             row = layout.row()
505             row.label(text="Selected: " + cmat.name, icon=("LOCKED" if locked else "UNLOCKED"))
506             row.operator("ml.lock", text=("Unlock" if locked else "Lock"))
507         except:
508             pass
509 '''
510 def register():
511     bpy.utils.register_module(__name__)
512     pass
513
514 def unregister():
515     bpy.utils.unregister_module(__name__)
516     pass
517
518 if __name__ == "__main__":
519     register()