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# <pep8 compliant>
20
21# ----------------------------------------------------------
22# support routines and general functions
23# Author: Antonio Vazquez (antonioya)
24#
25# ----------------------------------------------------------
26# noinspection PyUnresolvedReferences
27import bpy
28from os import path
29
30
31# --------------------------------------------------------------------
32# Get length Blender units
33# --------------------------------------------------------------------
34def get_blendunits(units):
35    if bpy.context.scene.unit_settings.system == "IMPERIAL":
36        return units * 0.3048
37    else:
38        return units
39
40
41# --------------------------------------------------------------------
42# Set normals
43# True= faces to inside
44# False= faces to outside
45# --------------------------------------------------------------------
46def set_normals(myobject, direction=False):
47    bpy.context.view_layer.objects.active = myobject
48    # go edit mode
49    bpy.ops.object.mode_set(mode='EDIT')
50    # select all faces
51    bpy.ops.mesh.select_all(action='SELECT')
52    # recalculate outside normals
53    bpy.ops.mesh.normals_make_consistent(inside=direction)
54    # go object mode again
55    bpy.ops.object.editmode_toggle()
56
57
58# --------------------------------------------------------------------
59# Remove doubles
60# --------------------------------------------------------------------
61def remove_doubles(myobject):
62    bpy.context.view_layer.objects.active = myobject
63    # go edit mode
64    bpy.ops.object.mode_set(mode='EDIT')
65    # select all faces
66    bpy.ops.mesh.select_all(action='SELECT')
67    # remove
68    bpy.ops.mesh.remove_doubles()
69    # go object mode again
70    bpy.ops.object.editmode_toggle()
71
72
73# --------------------------------------------------------------------
74# Set shade smooth
75# --------------------------------------------------------------------
76def set_smooth(myobject):
77    # deactivate others
78    for o in bpy.data.objects:
79        if o.select_get() is True:
80            o.select_set(False)
81
82    myobject.select_set(True)
83    bpy.context.view_layer.objects.active = myobject
84    if bpy.context.view_layer.objects.active.name == myobject.name:
85        bpy.ops.object.shade_smooth()
86
87
88# --------------------------------------------------------------------
89# Add modifier (subdivision)
90# --------------------------------------------------------------------
91def set_modifier_subsurf(myobject):
92    bpy.context.view_layer.objects.active = myobject
93    if bpy.context.view_layer.objects.active.name == myobject.name:
94        bpy.ops.object.modifier_add(type='SUBSURF')
95        for mod in myobject.modifiers:
96            if mod.type == 'SUBSURF':
97                mod.levels = 2
98
99
100# --------------------------------------------------------------------
101# Add modifier (mirror)
102# --------------------------------------------------------------------
103def set_modifier_mirror(myobject, axis="Y"):
104    bpy.ops.object.select_all(action='DESELECT')
105    myobject.select_set(True)
106    bpy.context.view_layer.objects.active = myobject
107    if bpy.context.view_layer.objects.active.name == myobject.name:
108        bpy.ops.object.modifier_add(type='MIRROR')
109        for mod in myobject.modifiers:
110            if mod.type == 'MIRROR':
111                if axis == "X":
112                    mod.use_axis[0] = True
113                else:
114                    mod.use__axis[0] = False
115
116                if axis == "Y":
117                    mod.use_axis[1] = True
118                else:
119                    mod.use_axis[1] = False
120
121                if axis == "Z":
122                    mod.use_axis[2] = True
123                else:
124                    mod.use_axis[2] = False
125
126                mod.use_clip = True
127
128
129# --------------------------------------------------------------------
130# Add modifier (array)
131# --------------------------------------------------------------------
132def set_modifier_array(myobject, axis, move, repeat, fix=False, fixmove=0, zmove=0):
133    bpy.ops.object.select_all(action='DESELECT')
134    myobject.select_set(True)
135    bpy.context.view_layer.objects.active = myobject
136    if bpy.context.view_layer.objects.active.name == myobject.name:
137        bpy.ops.object.modifier_add(type='ARRAY')
138        for mod in myobject.modifiers:
139            if mod.type == 'ARRAY':
140                if mod.name == "Array":
141                    mod.name = "Array_" + axis
142                    mod.count = repeat
143                    mod.use_constant_offset = fix
144                    if axis == "X":
145                        mod.relative_offset_displace[0] = move
146                        mod.constant_offset_displace[0] = fixmove
147                        mod.relative_offset_displace[1] = 0.0
148                        mod.constant_offset_displace[1] = 0.0
149                        mod.relative_offset_displace[2] = 0.0
150                        mod.constant_offset_displace[2] = zmove
151
152                    if axis == "Y":
153                        mod.relative_offset_displace[0] = 0.0
154                        mod.constant_offset_displace[0] = 0.0
155                        mod.relative_offset_displace[1] = move
156                        mod.constant_offset_displace[1] = fixmove
157                        mod.relative_offset_displace[2] = 0.0
158                        mod.constant_offset_displace[2] = 0.0
159
160
161# --------------------------------------------------------------------
162# Add modifier (curve)
163# --------------------------------------------------------------------
164def set_modifier_curve(myobject, mycurve):
165    bpy.context.view_layer.objects.active = myobject
166    if bpy.context.view_layer.objects.active.name == myobject.name:
167        bpy.ops.object.modifier_add(type='CURVE')
168        for mod in myobject.modifiers:
169            if mod.type == 'CURVE':
170                mod.deform_axis = 'POS_X'
171                mod.object = mycurve
172
173
174# --------------------------------------------------------------------
175# Add modifier (solidify)
176# --------------------------------------------------------------------
177def set_modifier_solidify(myobject, width):
178    bpy.context.view_layer.objects.active = myobject
179    if bpy.context.view_layer.objects.active.name == myobject.name:
180        bpy.ops.object.modifier_add(type='SOLIDIFY')
181        for mod in myobject.modifiers:
182            if mod.type == 'SOLIDIFY':
183                mod.thickness = width
184                mod.use_even_offset = True
185                mod.use_quality_normals = True
186                break
187
188
189# --------------------------------------------------------------------
190# Add modifier (boolean)
191# --------------------------------------------------------------------
192def set_modifier_boolean(myobject, bolobject):
193    bpy.context.view_layer.objects.active = myobject
194    if bpy.context.view_layer.objects.active.name == myobject.name:
195        bpy.ops.object.modifier_add(type='BOOLEAN')
196        mod = myobject.modifiers[len(myobject.modifiers) - 1]
197        mod.operation = 'DIFFERENCE'
198        mod.object = bolobject
199
200
201# --------------------------------------------------------------------
202# Set material to object
203# --------------------------------------------------------------------
204def set_material(myobject, mymaterial):
205    bpy.context.view_layer.objects.active = myobject
206    if bpy.context.view_layer.objects.active.name == myobject.name:
207        myobject.data.materials.append(mymaterial)
208
209
210# --------------------------------------------------------------------
211# Set material to selected faces
212# --------------------------------------------------------------------
213def set_material_faces(myobject, idx):
214    bpy.context.view_layer.objects.active = myobject
215    myobject.select_set(True)
216    bpy.context.object.active_material_index = idx
217    if bpy.context.view_layer.objects.active.name == myobject.name:
218        bpy.ops.object.mode_set(mode='EDIT')
219        bpy.ops.object.material_slot_assign()
220        # Deselect
221        bpy.ops.mesh.select_all(action='DESELECT')
222        bpy.ops.object.mode_set(mode='OBJECT')
223
224
225# --------------------------------------------------------------------
226# Select faces
227# --------------------------------------------------------------------
228def select_faces(myobject, selface, clear):
229    myobject.select_set(True)
230    bpy.context.view_layer.objects.active = myobject
231    if bpy.context.view_layer.objects.active.name == myobject.name:
232        # deselect everything
233        if clear:
234            bpy.ops.object.mode_set(mode='EDIT')
235            bpy.ops.mesh.select_all(action='DESELECT')
236
237        # reselect the originally selected face
238        bpy.ops.object.mode_set(mode='OBJECT')
239        myobject.data.polygons[selface].select = True
240
241
242# --------------------------------------------------------------------
243# Select vertices
244# --------------------------------------------------------------------
245def select_vertices(myobject, selvertices, clear=True):
246    myobject.select_set(True)
247    bpy.context.view_layer.objects.active = myobject
248    if bpy.context.view_layer.objects.active.name == myobject.name:
249        # deselect everything
250        if clear:
251            bpy.ops.object.mode_set(mode='EDIT')
252            bpy.ops.mesh.select_all(action='DESELECT')
253
254        # Select Vertices
255        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
256        sel_mode = bpy.context.tool_settings.mesh_select_mode
257
258        bpy.context.tool_settings.mesh_select_mode = [True, False, False]
259        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
260
261        for i in selvertices:
262            myobject.data.vertices[i].select = True
263
264        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
265        bpy.context.tool_settings.mesh_select_mode = sel_mode
266        bpy.ops.object.mode_set(mode='OBJECT')
267
268
269# --------------------------------------------------------------------
270# Mark Seam
271# --------------------------------------------------------------------
272def mark_seam(myobject):
273    # noinspection PyBroadException
274    try:
275        myobject.select_set(True)
276        bpy.context.view_layer.objects.active = myobject
277        if bpy.context.view_layer.objects.active.name == myobject.name:
278            bpy.ops.object.mode_set(mode='EDIT', toggle=False)
279            bpy.ops.mesh.mark_seam()
280            bpy.ops.object.mode_set(mode='OBJECT')
281    except:
282        bpy.ops.object.mode_set(mode='OBJECT')
283
284
285# --------------------------------------------------------------------
286# Unwrap mesh
287# --------------------------------------------------------------------
288def unwrap_mesh(myobject, allfaces=True):
289    # noinspection PyBroadException
290    try:
291        myobject.select_set(True)
292        bpy.context.view_layer.objects.active = myobject
293        if bpy.context.view_layer.objects.active.name == myobject.name:
294            # Unwrap
295            bpy.ops.object.mode_set(mode='EDIT', toggle=False)
296            if allfaces is True:
297                bpy.ops.mesh.select_all(action='DESELECT')
298                bpy.ops.mesh.select_all()
299            bpy.ops.uv.unwrap()
300            bpy.ops.object.mode_set(mode='OBJECT')
301    except:
302        bpy.ops.object.mode_set(mode='OBJECT')
303
304
305# --------------------------------------------------------------------
306# Get Node Index(multilanguage support)
307# --------------------------------------------------------------------
308def get_node_index(nodes, datatype):
309    idx = 0
310    for m in nodes:
311        if m.type == datatype:
312            return idx
313        idx += 1
314
315    # by default
316    return 1
317
318
319# --------------------------------------------------------------------
320# Create cycles diffuse material
321# --------------------------------------------------------------------
322def create_diffuse_material(matname, replace, r, g, b, rv=0.8, gv=0.8, bv=0.8, mix=0.1, twosides=False):
323    # Avoid duplicate materials
324    if replace is False:
325        matlist = bpy.data.materials
326        for m in matlist:
327            if m.name == matname:
328                return m
329    # Create material
330    mat = bpy.data.materials.new(matname)
331    mat.diffuse_color = (rv, gv, bv, 1.0)  # viewport color
332    mat.use_nodes = True
333    nodes = mat.node_tree.nodes
334
335    # support for multilanguage
336    node = nodes.new('ShaderNodeBsdfDiffuse')
337    node.name = 'Diffuse BSDF'
338    node.inputs[0].default_value = [r, g, b, 1]
339    node.location = 200, 320
340
341    node = nodes.new('ShaderNodeBsdfGlossy')
342    node.name = 'Glossy_0'
343    node.location = 200, 0
344
345    node = nodes.new('ShaderNodeMixShader')
346    node.name = 'Mix_0'
347    node.inputs[0].default_value = mix
348    node.location = 500, 160
349
350    node = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')]
351    node.location = 1100, 160
352
353    # Connect nodes
354    outn = nodes['Diffuse BSDF'].outputs[0]
355    inn = nodes['Mix_0'].inputs[1]
356    mat.node_tree.links.new(outn, inn)
357
358    outn = nodes['Glossy_0'].outputs[0]
359    inn = nodes['Mix_0'].inputs[2]
360    mat.node_tree.links.new(outn, inn)
361
362    if twosides is False:
363        outn = nodes['Mix_0'].outputs[0]
364        inn = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')].inputs[0]
365        mat.node_tree.links.new(outn, inn)
366
367    if twosides is True:
368        node = nodes.new('ShaderNodeNewGeometry')
369        node.name = 'Input_1'
370        node.location = -80, -70
371
372        node = nodes.new('ShaderNodeBsdfDiffuse')
373        node.name = 'Diffuse_1'
374        node.inputs[0].default_value = [0.30, 0.30, 0.30, 1]
375        node.location = 200, -280
376
377        node = nodes.new('ShaderNodeMixShader')
378        node.name = 'Mix_1'
379        node.inputs[0].default_value = mix
380        node.location = 800, -70
381
382        outn = nodes['Input_1'].outputs[6]
383        inn = nodes['Mix_1'].inputs[0]
384        mat.node_tree.links.new(outn, inn)
385
386        outn = nodes['Diffuse_1'].outputs[0]
387        inn = nodes['Mix_1'].inputs[2]
388        mat.node_tree.links.new(outn, inn)
389
390        outn = nodes['Mix_0'].outputs[0]
391        inn = nodes['Mix_1'].inputs[1]
392        mat.node_tree.links.new(outn, inn)
393
394        outn = nodes['Mix_1'].outputs[0]
395        inn = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')].inputs[0]
396        mat.node_tree.links.new(outn, inn)
397
398    return mat
399
400
401# --------------------------------------------------------------------
402# Create cycles translucent material
403# --------------------------------------------------------------------
404def create_translucent_material(matname, replace, r, g, b, rv=0.8, gv=0.8, bv=0.8, mix=0.1):
405    # Avoid duplicate materials
406    if replace is False:
407        matlist = bpy.data.materials
408        for m in matlist:
409            if m.name == matname:
410                return m
411    # Create material
412    mat = bpy.data.materials.new(matname)
413    mat.diffuse_color = (rv, gv, bv, 1.0)  # viewport color
414    mat.use_nodes = True
415    nodes = mat.node_tree.nodes
416
417    # support for multilanguage
418    node = nodes.new('ShaderNodeBsdfDiffuse')
419    node.name = 'Diffuse BSDF'
420    node.inputs[0].default_value = [r, g, b, 1]
421    node.location = 200, 320
422
423    node = nodes.new('ShaderNodeBsdfTranslucent')
424    node.name = 'Translucent_0'
425    node.location = 200, 0
426
427    node = nodes.new('ShaderNodeMixShader')
428    node.name = 'Mix_0'
429    node.inputs[0].default_value = mix
430    node.location = 500, 160
431
432    node = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')]
433    node.location = 1100, 160
434
435    # Connect nodes
436    outn = nodes['Diffuse BSDF'].outputs[0]
437    inn = nodes['Mix_0'].inputs[1]
438    mat.node_tree.links.new(outn, inn)
439
440    outn = nodes['Translucent_0'].outputs[0]
441    inn = nodes['Mix_0'].inputs[2]
442    mat.node_tree.links.new(outn, inn)
443
444    outn = nodes['Mix_0'].outputs[0]
445    inn = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')].inputs[0]
446    mat.node_tree.links.new(outn, inn)
447
448    return mat
449
450
451# --------------------------------------------------------------------
452# Create cycles glass material
453# --------------------------------------------------------------------
454def create_glass_material(matname, replace, rv=0.333, gv=0.342, bv=0.9):
455    # Avoid duplicate materials
456    if replace is False:
457        matlist = bpy.data.materials
458        for m in matlist:
459            if m.name == matname:
460                return m
461    # Create material
462    mat = bpy.data.materials.new(matname)
463    mat.use_nodes = True
464    mat.diffuse_color = (rv, gv, bv, 1.0)
465    nodes = mat.node_tree.nodes
466
467    # support for multilanguage
468    node = nodes[get_node_index(nodes, 'BSDF_DIFFUSE')]
469    mat.node_tree.nodes.remove(node)  # remove not used
470
471    node = nodes.new('ShaderNodeLightPath')
472    node.name = 'Light_0'
473    node.location = 10, 160
474
475    node = nodes.new('ShaderNodeBsdfRefraction')
476    node.name = 'Refraction_0'
477    node.inputs[2].default_value = 1  # IOR 1.0
478    node.location = 250, 400
479
480    node = nodes.new('ShaderNodeBsdfGlossy')
481    node.name = 'Glossy_0'
482    node.distribution = 'SHARP'
483    node.location = 250, 100
484
485    node = nodes.new('ShaderNodeBsdfTransparent')
486    node.name = 'Transparent_0'
487    node.location = 500, 10
488
489    node = nodes.new('ShaderNodeMixShader')
490    node.name = 'Mix_0'
491    node.inputs[0].default_value = 0.035
492    node.location = 500, 160
493
494    node = nodes.new('ShaderNodeMixShader')
495    node.name = 'Mix_1'
496    node.inputs[0].default_value = 0.1
497    node.location = 690, 290
498
499    node = nodes.new('ShaderNodeOutputMaterial')
500    node.name = 'OUTPUT_MATERIAL'
501    node.location = 920, 290
502
503    # Connect nodes
504    outn = nodes['Light_0'].outputs[1]
505    inn = nodes['Mix_1'].inputs[0]
506    mat.node_tree.links.new(outn, inn)
507
508    outn = nodes['Refraction_0'].outputs[0]
509    inn = nodes['Mix_0'].inputs[1]
510    mat.node_tree.links.new(outn, inn)
511
512    outn = nodes['Glossy_0'].outputs[0]
513    inn = nodes['Mix_0'].inputs[2]
514    mat.node_tree.links.new(outn, inn)
515
516    outn = nodes['Mix_0'].outputs[0]
517    inn = nodes['Mix_1'].inputs[1]
518    mat.node_tree.links.new(outn, inn)
519
520    outn = nodes['Transparent_0'].outputs[0]
521    inn = nodes['Mix_1'].inputs[2]
522    mat.node_tree.links.new(outn, inn)
523
524    outn = nodes['Mix_1'].outputs[0]
525    inn = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')].inputs[0]
526    mat.node_tree.links.new(outn, inn)
527
528    return mat
529
530
531# ---------------------------------------------
532# Create cycles transparents material
533# --------------------------------------------------------------------
534def create_transparent_material(matname, replace, r=1, g=1, b=1, alpha=0):
535    # Avoid duplicate materials
536    if replace is False:
537        matlist = bpy.data.materials
538        for m in matlist:
539            if m.name == matname:
540                return m
541    # Create material
542    mat = bpy.data.materials.new(matname)
543    mat.use_nodes = True
544    mat.diffuse_color = (r, g, b, 1.0)
545    nodes = mat.node_tree.nodes
546
547    # support for multilanguage
548    node = nodes[get_node_index(nodes, 'BSDF_DIFFUSE')]
549    mat.node_tree.nodes.remove(node)  # remove not used
550
551    node = nodes.new('ShaderNodeBsdfTransparent')
552    node.name = 'Transparent_0'
553    node.location = 250, 160
554    node.inputs[0].default_value = [r, g, b, alpha]
555
556    node = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')]
557    node.location = 700, 160
558
559    # Connect nodes
560    outn = nodes['Transparent_0'].outputs[0]
561    inn = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')].inputs[0]
562    mat.node_tree.links.new(outn, inn)
563
564    return mat
565
566
567# --------------------------------------------------------------------
568# Create cycles glossy material
569# --------------------------------------------------------------------
570def create_glossy_material(matname, replace, r, g, b, rv=0.578, gv=0.555, bv=0.736, rvalue=0.2):
571    # Avoid duplicate materials
572    if replace is False:
573        matlist = bpy.data.materials
574        for m in matlist:
575            if m.name == matname:
576                return m
577    # Create material
578    mat = bpy.data.materials.new(matname)
579    mat.use_nodes = True
580    mat.diffuse_color = (rv, gv, bv, 1.0)
581    nodes = mat.node_tree.nodes
582
583    # support for multilanguage
584    node = nodes[get_node_index(nodes, 'BSDF_DIFFUSE')]
585    mat.node_tree.nodes.remove(node)  # remove not used
586
587    node = nodes.new('ShaderNodeBsdfGlossy')
588    node.name = 'Glossy_0'
589    node.inputs[0].default_value = [r, g, b, 1]
590    node.inputs[1].default_value = rvalue
591    node.location = 200, 160
592
593    node = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')]
594    node.location = 700, 160
595
596    # Connect nodes
597    outn = nodes['Glossy_0'].outputs[0]
598    inn = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')].inputs[0]
599    mat.node_tree.links.new(outn, inn)
600
601    return mat
602
603
604# --------------------------------------------------------------------
605# Create cycles emission material
606# --------------------------------------------------------------------
607def create_emission_material(matname, replace, r, g, b, energy):
608    # Avoid duplicate materials
609    if replace is False:
610        matlist = bpy.data.materials
611        for m in matlist:
612            if m.name == matname:
613                return m
614    # Create material
615    mat = bpy.data.materials.new(matname)
616    mat.use_nodes = True
617    nodes = mat.node_tree.nodes
618
619    # support for multilanguage
620    node = nodes[get_node_index(nodes, 'BSDF_DIFFUSE')]
621    mat.node_tree.nodes.remove(node)  # remove not used
622
623    node = nodes.new('ShaderNodeEmission')
624    node.name = 'Emission_0'
625    node.inputs[0].default_value = [r, g, b, 1]
626    node.inputs[1].default_value = energy
627    node.location = 200, 160
628
629    node = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')]
630    node.location = 700, 160
631
632    # Connect nodes
633    outn = nodes['Emission_0'].outputs[0]
634    inn = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')].inputs[0]
635    mat.node_tree.links.new(outn, inn)
636
637    return mat
638
639
640# --------------------------------------------------------------------
641# Create cycles glass material
642# --------------------------------------------------------------------
643def create_old_glass_material(matname, replace, rv=0.352716, gv=0.760852, bv=0.9):
644    # Avoid duplicate materials
645    if replace is False:
646        matlist = bpy.data.materials
647        for m in matlist:
648            if m.name == matname:
649                return m
650    # Create material
651    mat = bpy.data.materials.new(matname)
652    mat.use_nodes = True
653    mat.diffuse_color = (rv, gv, bv, 1.0)
654    nodes = mat.node_tree.nodes
655
656    # support for multilanguage
657    node = nodes[get_node_index(nodes, 'BSDF_DIFFUSE')]
658    mat.node_tree.nodes.remove(node)  # remove not used
659
660    node = nodes.new('ShaderNodeLightPath')
661    node.name = 'Light_0'
662    node.location = 10, 160
663
664    node = nodes.new('ShaderNodeBsdfGlass')
665    node.name = 'Glass_0'
666    node.location = 250, 300
667
668    node = nodes.new('ShaderNodeBsdfTransparent')
669    node.name = 'Transparent_0'
670    node.location = 250, 0
671
672    node = nodes.new('ShaderNodeMixShader')
673    node.name = 'Mix_0'
674    node.inputs[0].default_value = 0.1
675    node.location = 500, 160
676
677    node = nodes.new('ShaderNodeMixShader')
678    node.name = 'Mix_1'
679    node.inputs[0].default_value = 0.1
680    node.location = 690, 290
681
682    node = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')]
683    node.location = 920, 290
684
685    # Connect nodes
686    outn = nodes['Light_0'].outputs[1]
687    inn = nodes['Mix_0'].inputs[0]
688    mat.node_tree.links.new(outn, inn)
689
690    outn = nodes['Light_0'].outputs[2]
691    inn = nodes['Mix_1'].inputs[0]
692    mat.node_tree.links.new(outn, inn)
693
694    outn = nodes['Glass_0'].outputs[0]
695    inn = nodes['Mix_0'].inputs[1]
696    mat.node_tree.links.new(outn, inn)
697
698    outn = nodes['Transparent_0'].outputs[0]
699    inn = nodes['Mix_0'].inputs[2]
700    mat.node_tree.links.new(outn, inn)
701
702    outn = nodes['Mix_0'].outputs[0]
703    inn = nodes['Mix_1'].inputs[1]
704    mat.node_tree.links.new(outn, inn)
705
706    outn = nodes['Mix_1'].outputs[0]
707    inn = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')].inputs[0]
708    mat.node_tree.links.new(outn, inn)
709
710    return mat
711
712
713# --------------------------------------------------------------------
714# Create cycles brick texture material
715# --------------------------------------------------------------------
716def create_brick_material(matname, replace, r, g, b, rv=0.8, gv=0.636, bv=0.315):
717    # Avoid duplicate materials
718    if replace is False:
719        matlist = bpy.data.materials
720        for m in matlist:
721            if m.name == matname:
722                return m
723    # Create material
724    mat = bpy.data.materials.new(matname)
725    mat.use_nodes = True
726    mat.diffuse_color = (rv, gv, bv, 1.0)
727    nodes = mat.node_tree.nodes
728
729    # support for multilanguage
730    node = nodes[get_node_index(nodes, 'BSDF_DIFFUSE')]
731    node.name = 'Diffuse BSDF'
732    node.label = 'Diffuse BSDF'
733
734    node.inputs[0].default_value = [r, g, b, 1]
735    node.location = 500, 160
736
737    node = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')]
738    node.location = 700, 160
739
740    node = nodes.new('ShaderNodeTexBrick')
741    node.name = 'Brick_0'
742    node.inputs[3].default_value = [0.407, 0.411, 0.394, 1]  # mortar color
743    node.inputs[4].default_value = 3  # scale
744    node.inputs[5].default_value = 0.001  # mortar
745    node.inputs[7].default_value = 0.60  # size_w
746    node.inputs[8].default_value = 0.30  # size_h
747    node.location = 300, 160
748
749    node = nodes.new('ShaderNodeRGB')
750    node.name = 'RGB_0'
751    node.outputs[0].default_value = [r, g, b, 1]
752    node.location = 70, 160
753
754    # Connect nodes
755    outn = nodes['RGB_0'].outputs['Color']
756    inn = nodes['Brick_0'].inputs['Color1']
757    mat.node_tree.links.new(outn, inn)
758
759    inn = nodes['Brick_0'].inputs['Color2']
760    mat.node_tree.links.new(outn, inn)
761
762    outn = nodes['Brick_0'].outputs['Color']
763    inn = nodes['Diffuse BSDF'].inputs['Color']
764    mat.node_tree.links.new(outn, inn)
765
766    return mat
767
768
769# --------------------------------------------------------------------
770# Create cycles fabric texture material
771# --------------------------------------------------------------------
772def create_fabric_material(matname, replace, r, g, b, rv=0.8, gv=0.636, bv=0.315):
773    # Avoid duplicate materials
774    if replace is False:
775        matlist = bpy.data.materials
776        for m in matlist:
777            if m.name == matname:
778                return m
779    # Create material
780    mat = bpy.data.materials.new(matname)
781    mat.use_nodes = True
782    mat.diffuse_color = (rv, gv, bv, 1.0)
783    nodes = mat.node_tree.nodes
784
785    # support for multilanguage
786    node = nodes.new('ShaderNodeBsdfDiffuse')
787    node.name = 'Diffuse BSDF'
788    node.inputs[0].default_value = [r, g, b, 1]
789    node.location = 810, 270
790
791    node = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')]
792    node.location = 1210, 320
793
794    node = nodes.new('ShaderNodeTexCoord')
795    node.name = 'UVCoordinates'
796    node.location = 26, 395
797
798    node = nodes.new('ShaderNodeMapping')
799    node.name = 'UVMapping'
800    node.location = 266, 380
801    node.inputs['Scale'].default_value[0] = 1000
802    node.inputs['Scale'].default_value[1] = 1000
803    node.inputs['Scale'].default_value[2] = 1000
804
805    # ===========================================================================
806    # Image texture
807    # ===========================================================================
808    # Load image file.
809
810    realpath = path.join(path.dirname(__file__), "images", "fabric_diffuse.png")
811    print("Loading: " + realpath)
812    try:
813        img = bpy.data.images.load(realpath)
814    except:
815        raise NameError("Cannot load image %s" % realpath)
816
817    # Create image texture from image
818    ctex = bpy.data.textures.new('ColorTex', type='IMAGE')
819    ctex.image = img
820
821    node = nodes.new('ShaderNodeTexImage')
822    node.name = 'Image1'
823    node.image = ctex.image
824    node.location = 615, 350
825
826    node = nodes.new('ShaderNodeBsdfTransparent')
827    node.name = 'Transparent1'
828    node.location = 810, 395
829    node.inputs[0].default_value = [r, g, b, 1]
830
831    node = nodes.new('ShaderNodeAddShader')
832    node.name = 'Add1'
833    node.location = 1040, 356
834
835    # Connect nodes
836    outn = nodes['UVCoordinates'].outputs['UV']
837    inn = nodes['UVMapping'].inputs['Vector']
838    mat.node_tree.links.new(outn, inn)
839
840    outn = nodes['UVMapping'].outputs['Vector']
841    inn = nodes['Image1'].inputs['Vector']
842    mat.node_tree.links.new(outn, inn)
843
844    outn = nodes['Image1'].outputs['Color']
845    inn = nodes['Diffuse BSDF'].inputs['Color']
846    mat.node_tree.links.new(outn, inn)
847
848    outn = nodes['Transparent1'].outputs['BSDF']
849    inn = nodes['Add1'].inputs[0]
850    mat.node_tree.links.new(outn, inn)
851
852    outn = nodes['Diffuse BSDF'].outputs['BSDF']
853    inn = nodes['Add1'].inputs[1]
854    mat.node_tree.links.new(outn, inn)
855
856    outn = nodes['Add1'].outputs['Shader']
857    inn = nodes[get_node_index(nodes, 'OUTPUT_MATERIAL')].inputs[0]
858    mat.node_tree.links.new(outn, inn)
859
860    return mat
861
862
863# --------------------------------------------------------------------
864# Copy bin file
865# --------------------------------------------------------------------
866def copy_binfile(fromfile, tofile):
867    with open(fromfile, 'rb') as f1:
868        with open(tofile, 'wb') as f2:
869            while True:
870                mybytes = f1.read(1024)
871                if mybytes:
872                    f2.write(mybytes)
873                else:
874                    break
875
876
877# --------------------------------------------------------------------
878# Parent object (keep positions)
879# --------------------------------------------------------------------
880def parentobject(parentobj, childobj):
881    # noinspection PyBroadException
882    try:
883        bpy.ops.object.select_all(action='DESELECT')
884        bpy.context.view_layer.objects.active = parentobj
885        parentobj.select_set(True)
886        childobj.select_set(True)
887        bpy.ops.object.parent_set(type='OBJECT', keep_transform=False)
888        return True
889    except:
890        return False
891
892
893# ------------------------------------------------------------------------------
894# Create control box
895#
896# objName: Object name
897# x: size x axis
898# y: size y axis
899# z: size z axis
900# tube: True create a tube, False only sides
901# ------------------------------------------------------------------------------
902def create_control_box(objname, x, y, z, tube=True):
903    myvertex = [(-x / 2, 0, 0.0),
904                (-x / 2, y, 0.0),
905                (x / 2, y, 0.0),
906                (x / 2, 0, 0.0),
907                (-x / 2, 0, z),
908                (-x / 2, y, z),
909                (x / 2, y, z),
910                (x / 2, 0, z)]
911
912    if tube is True:
913        myfaces = [(0, 1, 2, 3), (0, 4, 5, 1), (1, 5, 6, 2), (3, 7, 4, 0), (2, 6, 7, 3), (5, 4, 7, 6)]
914    else:
915        myfaces = [(0, 4, 5, 1), (2, 6, 7, 3)]
916
917    mesh = bpy.data.meshes.new(objname)
918    myobject = bpy.data.objects.new(objname, mesh)
919
920    myobject.location = bpy.context.scene.cursor.location
921    bpy.context.collection.objects.link(myobject)
922
923    mesh.from_pydata(myvertex, [], myfaces)
924    mesh.update(calc_edges=True)
925
926    return myobject
927
928
929# ------------------------------------------------------------------------------
930# Remove all children objects
931# ------------------------------------------------------------------------------
932def remove_children(myobject):
933    # Remove children
934    for child in myobject.children:
935        # noinspection PyBroadException
936        try:
937            # noinspection PyBroadException
938            try:
939                # remove child relationship
940                for grandchild in child.children:
941                    grandchild.parent = None
942                # remove modifiers
943                for mod in child.modifiers:
944                    bpy.ops.object.modifier_remove(name=mod.name)
945            except:
946                pass
947            # clear child data
948            if child.type == 'MESH':
949                old = child.data
950                child.select_set(True)
951                bpy.ops.object.delete()
952                bpy.data.meshes.remove(old)
953            if child.type == 'CURVE':
954                child.select_set(True)
955                bpy.ops.object.delete()
956        except:
957            pass
958
959
960# --------------------------------------------------------------------
961# Get all parents
962# --------------------------------------------------------------------
963def get_allparents(myobj):
964    obj = myobj
965    mylist = []
966    while obj.parent is not None:
967        mylist.append(obj)
968        objp = obj.parent
969        obj = objp
970
971    mylist.append(obj)
972
973    return mylist
974
975
976# --------------------------------------------------------------------
977# Verify all faces are in vertice group to avoid Blander crash
978#
979# Review the faces array and remove any vertex out of the range
980# this avoid any bug that can appear avoiding Blender crash
981# --------------------------------------------------------------------
982def check_mesh_errors(myvertices, myfaces):
983    vmax = len(myvertices)
984
985    f = 0
986    for face in myfaces:
987        for v in face:
988            if v < 0 or v > vmax:
989                print("Face=" + str(f) + "->removed vertex=" + str(v))
990                myfaces[f].remove(v)
991        f += 1
992
993    return myfaces
994