Professional Documents
Culture Documents
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import bpy
import os
from os.path import join, dirname, realpath
import bpy.utils.previews
from bpy_extras import view3d_utils
from bpy.props import IntProperty, FloatProperty, BoolProperty, StringProperty,
EnumProperty
import rna_keymap_ui
import mathutils
from mathutils import Matrix, Vector, Euler
bl_info = {
"name": "Fluent : Materializer",
"description": "程序材质工具套装.",
"author": "Rudy MICHAU CG 资源网-CGZY.NET",
"version": (1, 1, 1),
"blender": (3, 2, 0),
"location": "Node Editor Toolbar",
"wiki_url": "",
"category": "Shader"}
temp = None
viewport_navigation_events = [
'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE',
'TRACKPADPAN', 'TRACKPADZOOM', 'MOUSEROTATE', 'MOUSESMARTZOOM',
'NDOF_MOTION'
]
icons_dir = join(dirname(realpath(__file__)))
image_size = (32, 32)
def load_icons():
global fluent_icons_collection
global fluent_icons_loaded
custom_icons = bpy.utils.previews.new()
icons_dir = join(dirname(realpath(__file__)),'icons')
fluent_icons_collection["main"] = custom_icons
fluent_icons_loaded = True
return fluent_icons_collection["main"]
def clear_icons():
global fluent_icons_loaded
for icon in fluent_icons_collection.values():
bpy.utils.previews.remove(icon)
fluent_icons_collection.clear()
fluent_icons_loaded = False
main_dir = join(dirname(realpath(__file__)))
blender_dir = join(main_dir,'Blender_Files')
blender_file = join(blender_dir,'Material_Studying.blend')
file_path_node_tree = join(blender_file,'NodeTree')
file_path_shader = join(blender_file,'Material')
preview_collections = {}
preview_dir = join(dirname(realpath(__file__)),'thumbnails')
imperfections_dir = join(preview_dir,'imperfections')
grunges_dir = join(preview_dir,'grunges')
patterns_dir = join(preview_dir,'patterns')
liquid_dir = join(preview_dir,'liquid')
city_dir = join(preview_dir,'city')
normals_dir = join(preview_dir,'normals')
fabric_dir = join(preview_dir,'fabric')
metal_dir = join(preview_dir,'metal')
shaders_dir = join(preview_dir,'shaders')
screen_dir = join(preview_dir,'screen')
wood_dir = join(preview_dir,'wood')
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = imperfections_dir
pcoll.my_previews = ()
preview_collections["imperfections"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = grunges_dir
pcoll.my_previews = ()
preview_collections["grunges"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = patterns_dir
pcoll.my_previews = ()
preview_collections["patterns"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = liquid_dir
pcoll.my_previews = ()
preview_collections["liquid"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = city_dir
pcoll.my_previews = ()
preview_collections["city"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = normals_dir
pcoll.my_previews = ()
preview_collections["normals"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = fabric_dir
pcoll.my_previews = ()
preview_collections["fabric"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = metal_dir
pcoll.my_previews = ()
preview_collections["metal"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = shaders_dir
pcoll.my_previews = ()
preview_collections["shaders"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = screen_dir
pcoll.my_previews = ()
preview_collections["screen"] = pcoll
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = wood_dir
pcoll.my_previews = ()
preview_collections["wood"] = pcoll
def make_oops(msg):
global CR
def oops(self, context):
for m in msg:
self.layout.label(text=m)
return oops
def get_addon_preferences():
addon_key = __package__.split(".")[0]
addon_prefs = bpy.context.preferences.addons[addon_key].preferences
return addon_prefs
obj.hide_set(False)
obj.select_set(False)
return True
def get_material():
for o in bpy.context.scene.objects:
print(o.name)
return bpy.context.scene.objects
def get_node_tree(material):
return material.node_tree
def get_nodes(material):
return get_node_tree(material).nodes
def get_selected_nodes(nodes):
selected_nodes = []
for n in nodes:
if n.select:
selected_nodes.append(n)
return selected_nodes
def add_node_group(group_name):
nodes = get_nodes()
if not bpy.data.node_groups.get(group_name):
bpy.ops.wm.append(filename=group_name, directory=file_path_node_tree)
if bpy.data.node_groups.get(group_name):
new_group = nodes.new(type='ShaderNodeGroup')
new_group.node_tree = bpy.data.node_groups.get(group_name)
new_group.name = group_name
return new_group
def enum_previews_from_directory_items(pcoll):
global preview_collections
enum_items = []
if bpy.context is None:
return enum_items
directory = pcoll.my_previews_dir
pcoll.my_previews = enum_items
pcoll.my_previews_dir = directory
return pcoll.my_previews
def import_node_group(group_name):
material = bpy.context.active_object.active_material
tree = material.node_tree
nodes = tree.nodes
if not bpy.data.node_groups.get(group_name):
bpy.ops.wm.append(filename=group_name, directory=file_path_node_tree)
if bpy.data.node_groups.get(group_name):
new_group = nodes.new(type='ShaderNodeGroup')
new_group.node_tree = bpy.data.node_groups.get(group_name)
new_group.name = group_name
return new_group
def deselect_nodes(nodes):
for node in nodes:
node.select = False
visible_list = bpy.context.visible_objects
depsgraph = bpy.context.evaluated_depsgraph_get()
if result['success']:
length_squared = (obj.matrix_world @ result['hit'] -
result.get('ray_origin')).length_squared
if best_obj is None or length_squared < best_length_squared:
best_length_squared = length_squared
best_obj = obj
return best_obj
matrix = obj.matrix_world.copy()
# get the ray relative to the object
matrix_inv = matrix.inverted()
ray_origin_obj = matrix_inv @ ray_origin
ray_target_obj = matrix_inv @ ray_target
ray_direction_obj = ray_target_obj - ray_origin_obj
return {
'success': success,
'hit': location,
'normal': normal,
'face_index': face_index,
'ray_origin': ray_origin,
'obj': obj
}
def cast_local2global(dico):
if dico['success']:
obj = dico['obj']
matrix = obj.matrix_world
converted = {
'success': dico['success'],
'hit': matrix @ dico['hit'],
'normal': matrix.to_3x3() @ dico['normal'],
'face_index': dico['face_index'],
'ray_origin': dico['ray_origin'],
'obj': dico['obj']
}
return converted
else:
print('Cast fail, impossible to convert.')
def pass_through(event):
return (event.type in viewport_navigation_events) \
or (event.type in viewport_navigation_keys and not event.shift) \
or (event.alt and event.type == 'LEFTMOUSE') \
or (event.alt and event.type == 'RIGHTMOUSE')
def make_single_user_node(node):
original = node.node_tree
single = original.copy()
node.node_tree = single
return single
class FLUENT_SHADER_OT_addNodes(Operator):
bl_idname = 'fluent.add_nodes'
bl_description = '添加节点设置'
bl_category = 'Node'
bl_label = '添加设置'
choice: bpy.props.StringProperty()
self.grab_at_the_end = True
try:
material = bpy.context.active_object.active_material
tree = material.node_tree
nodes = tree.nodes
selected_nodes = bpy.context.selected_nodes
except:
obj = bpy.context.active_object
mat = bpy.data.materials.new(name="F_Material")
mat.use_nodes = True
if obj.data.materials:
obj.data.materials[0] = mat
else:
# no slots
obj.data.materials.append(mat)
material = bpy.context.active_object.active_material
tree = material.node_tree
nodes = tree.nodes
selected_nodes = []
for node in nodes:
if node.type == 'BSDF_PRINCIPLED':
node.select = True
nodes.active = node
selected_nodes.append(node)
else:
node.select = False
try:
if nodes.active and nodes.active.select:
previous_node = nodes.active
else:
previous_node = None
# previous_node = bpy.context.selected_nodes[0]
except:
previous_node = None
if self.choice == 'imperfections':
group_name = bpy.context.scene.FluentShaderProps.f_imperfections
elif self.choice == 'grunges':
group_name = bpy.context.scene.FluentShaderProps.f_grunges
elif self.choice == 'patterns':
group_name = bpy.context.scene.FluentShaderProps.f_patterns
elif self.choice == 'liquid':
group_name = bpy.context.scene.FluentShaderProps.f_liquid
elif self.choice == 'city':
group_name = bpy.context.scene.FluentShaderProps.f_city
elif self.choice == 'normals':
group_name = bpy.context.scene.FluentShaderProps.f_normals
elif self.choice == 'fabric':
group_name = bpy.context.scene.FluentShaderProps.f_fabric
elif self.choice == 'metal':
group_name = bpy.context.scene.FluentShaderProps.f_metal
elif self.choice == 'shaders':
group_name = bpy.context.scene.FluentShaderProps.f_shaders
elif self.choice == 'wood':
group_name = bpy.context.scene.FluentShaderProps.f_wood
elif self.choice == 'screen':
group_name = bpy.context.scene.FluentShaderProps.f_screen
elif self.choice in ['Smart Shader 2 layers', 'Smart Shader 3 layers']:
try:
if previous_node.type == 'BSDF_PRINCIPLED':
group_name = '混合图层'
else:
bpy.context.window_manager.popup_menu(make_oops(['Select a
Principled BSDF before to call this function.']), title="How to use ?",
icon='INFO')
return{'FINISHED'}
except:
bpy.context.window_manager.popup_menu(make_oops(['Select a
Principled BSDF before to call this function.']), title="How to use ?",
icon='INFO')
return{'FINISHED'}
if group_name == None:
group_name = self.choice
new_group = import_node_group(group_name)
if previous_node:
new_group.location = previous_node.location
new_group.location[0] -= previous_node.dimensions[0] *
(1/context.preferences.system.ui_scale) + 50
layer_1 = import_node_group('Layer')
link(material, layer_1, 'Color', mix_1, 'Color 1')
link(material, layer_1, 'Metallic', mix_1, 'Metallic 1')
link(material, layer_1, 'Roughness', mix_1, 'Roughness 1')
link(material, layer_1, 'Normal', mix_1, 'Normal 1')
try:
link(material, layer_1, 'Emission Strength', mix_1, 'Emission
Strength 1')
except:pass
layer_2 = import_node_group('Layer')
link(material, layer_2, 'Color', mix_1, 'Color 2')
link(material, layer_2, 'Metallic', mix_1, 'Metallic 2')
link(material, layer_2, 'Roughness', mix_1, 'Roughness 2')
link(material, layer_2, 'Normal', mix_1, 'Normal 2')
try:
link(material, layer_2, 'Emission Strength', mix_1, 'Emission
Strength 2')
except:pass
layer_1 = import_node_group('Layer')
link(material, layer_1, 'Color', mix_1, 'Color 1')
link(material, layer_1, 'Metallic', mix_1, 'Metallic 1')
link(material, layer_1, 'Roughness', mix_1, 'Roughness 1')
link(material, layer_1, 'Normal', mix_1, 'Normal 1')
try:
link(material, layer_1, 'Emission Strength', mix_1, 'Emission
Strength 1')
except:pass
layer_2 = import_node_group('Layer')
link(material, layer_2, 'Color', mix_1, 'Color 2')
link(material, layer_2, 'Metallic', mix_1, 'Metallic 2')
link(material, layer_2, 'Roughness', mix_1, 'Roughness 2')
link(material, layer_2, 'Normal', mix_1, 'Normal 2')
try:
link(material, layer_2, 'Emission Strength', mix_1, 'Emission
Strength 2')
except:pass
layer_3 = import_node_group('Layer')
link(material, layer_3, 'Color', mix_2, 'Color 2')
link(material, layer_3, 'Metallic', mix_2, 'Metallic 2')
link(material, layer_3, 'Roughness', mix_2, 'Roughness 2')
link(material, layer_3, 'Normal', mix_2, 'Normal 2')
try:
link(material, layer_3, 'Emission Strength', mix_2, 'Emission
Strength 2')
except:pass
deselect_nodes(nodes)
new_group.select = True
nodes.active = new_group
self.new_group = new_group
temp = {'last_addition':new_group, 'previous_node':previous_node}
if 'Scale' in new_group.inputs:
new_group.inputs['Scale'].default_value *=
context.scene.FluentShaderProps.scale_scale
return {'FINISHED'}
class FLUENT_SHADER_PT_ViewPanel(bpy.types.Panel):
"Fluent Shader Editor"
bl_label = "着色器"
bl_name = "Shader"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = 'Fluent'
box = layout.box()
row = box.row()
row.prop(context.scene.FluentShaderProps, 'show_bake_pbr',
icon="TRIA_DOWN" if context.scene.FluentShaderProps.show_bake_pbr
else "TRIA_RIGHT",
icon_only=True, emboss=False
)
row.label(text="烘焙 PBR")
if context.scene.FluentShaderProps.show_bake_pbr:
selection_col = box.column(align=True)
selection_col.prop(context.scene.FluentShaderProps, 'bake_make_color')
selection_col.prop(context.scene.FluentShaderProps,
'bake_make_metallic')
selection_col.prop(context.scene.FluentShaderProps,
'bake_make_roughness')
selection_col_row = selection_col.row()
selection_col_row.prop(context.scene.FluentShaderProps,
'bake_make_normal')
selection_col_row.prop(context.scene.FluentShaderProps,
'bake_normal_directx')
selection_col.prop(context.scene.FluentShaderProps,
'bake_make_emission')
selection_col.prop(context.scene.FluentShaderProps, 'bake_make_ao')
# layout.prop(context.scene.FluentShaderProps, 'bake_make_alpha')
selection_col.prop(context.scene.FluentShaderProps,
'bake_make_selected_to_active')
if context.scene.FluentShaderProps.bake_make_selected_to_active:
selection_col.prop(context.scene.FluentShaderProps,
'bake_make_selected_to_active_extrusion')
selection_col.operator('fluent.bakepbrmaps', text='烘焙 PBR 贴图')
box = layout.box()
row = box.row()
row.prop(context.scene.FluentShaderProps, 'show_bake_mask',
icon="TRIA_DOWN" if context.scene.FluentShaderProps.show_bake_mask
else "TRIA_RIGHT",
icon_only=True, emboss=False
)
row.label(text="烘焙蒙版")
if context.scene.FluentShaderProps.show_bake_mask:
selection_col = box.column(align=True)
selection_col.operator(FLUENT_OT_BakeMask.bl_idname, text='所有边缘和型
腔').choice = 'ALL'
selection_col.operator(FLUENT_OT_BakeMask.bl_idname, text='只有边
缘').choice = 'EDGES'
selection_col.operator(FLUENT_OT_BakeMask.bl_idname, text='只有凹
洞').choice = 'CAVITY'
selection_col.operator(FLUENT_OT_BakeMask.bl_idname, text='仅选择节
点').choice = 'SELECTED'
# box = layout.box()
# row = box.row()
# row.prop(context.scene.FluentShaderProps, 'show_bake_decal',
# icon="TRIA_DOWN" if
context.scene.FluentShaderProps.show_bake_decal else "TRIA_RIGHT",
# icon_only=True, emboss=False
# )
# row.label(text="贴花")
# if context.scene.FluentShaderProps.show_bake_decal:
# selection_col = box.column(align=True)
# selection_col.operator(FLUENT_SHADER_OT_MoveDecal.bl_idname, text='移
动贴花')
layout.operator(FLUENT_SHADER_OT_FindHelp.bl_idname, text='帮助',
icon='HELP')
class FLUENT_SHADER_PT_Library(bpy.types.Panel):
"Fluent 节点资源库"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = 'Fluent'
bl_label = '资源库 CGZY.NET'
@classmethod
def poll(cls, context):
if (context.space_data.tree_type == "ShaderNodeTree" and
context.space_data.shader_type == "OBJECT"):
return True
else:
return False
#####################################################
class FLUENT_OT_BakeMaps(Operator):
"烘焙 PBR 贴图\nCTRL+SHIFT+ALT 寻求帮助"
bl_idname = "fluent.bakepbrmaps"
bl_label="烘焙 PBR 贴图"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if active_object('GET'):
return True
else:
return False
try:
file_path = bpy.data.filepath
file_name = file_path.split('\\')[-1]
path = file_path.split(file_name)[0]
path_texture = path+'Textures\\'
if not os.path.exists(path_texture):
os.makedirs(path_texture)
folder_made = True
except:
folder_made = False
# ajoute une node image dans chaque matériau
img_nodes = []
for m in self.object.material_slots:
nodes = get_nodes(m.material)
img_node = nodes.new(type='ShaderNodeTexImage')
img_node.name = '#imgtemp'
img_nodes.append([nodes, img_node])
if context.scene.FluentShaderProps.bake_make_color:
# créer l'image Color vide
color_img = bpy.data.images.new(self.object.name + '_' + "Color",
width=self.size, height=self.size)
color_img.file_format = 'PNG'
if folder_made:
color_img.filepath_raw = path_texture + color_img.name + '.png'
bpy.ops.object.bake(type='COMBINED',
use_selected_to_active=self.selected_to_active, cage_extrusion=self.extrusion)
self.restore_materials_outputs()
try:
color_img.save()
except:
self.report({'WARNING'}, "Blender fails to save the files on your
system. Save it manually.")
if context.scene.FluentShaderProps.bake_make_roughness:
# créer l'image Roughness vide
roughness_img = bpy.data.images.new(self.object.name+'_'+"Roughness",
width=self.size, height=self.size, is_data=True)
roughness_img.file_format = 'PNG'
if folder_made:
roughness_img.filepath_raw = path_texture+roughness_img.name+'.png'
self.link_socket_to_materials_outputs('roughness')
bpy.ops.object.bake(type='COMBINED',
use_selected_to_active=self.selected_to_active, cage_extrusion=self.extrusion)
self.restore_materials_outputs()
try:
roughness_img.save()
except:
self.report({'WARNING'}, "Blender fails to save the files on your
system. Save it manually.")
if context.scene.FluentShaderProps.bake_make_metallic:
# créer l'image metallic vide
metallic_img = bpy.data.images.new(self.object.name+'_'+"Metallic",
width=self.size, height=self.size, is_data=True)
metallic_img.file_format = 'PNG'
if folder_made:
metallic_img.filepath_raw = path_texture+metallic_img.name+'.png'
self.link_socket_to_materials_outputs('metallic')
bpy.ops.object.bake(type='COMBINED',
use_selected_to_active=self.selected_to_active, cage_extrusion=self.extrusion)
self.restore_materials_outputs()
try:
metallic_img.save()
except:
self.report({'WARNING'}, "Blender fails to save the files on your
system. Save it manually.")
if context.scene.FluentShaderProps.bake_make_normal:
# créer l'image normal vide
normal_img = bpy.data.images.new(self.object.name+'_'+"Normal",
width=self.size, height=self.size, is_data=True, float_buffer=True)
normal_img.file_format = 'PNG'
if folder_made:
normal_img.filepath_raw = path_texture+normal_img.name+'.png'
bpy.ops.object.bake(type='NORMAL',
use_selected_to_active=self.selected_to_active, cage_extrusion=self.extrusion)
if context.scene.FluentShaderProps.bake_normal_directx:
normal_directx_img = bpy.data.images.new(self.object.name + '_' +
"DirectX_Normal", width=self.size, height=self.size, is_data=True,
float_buffer=True)
normal_directx_img.file_format = 'PNG'
if folder_made:
normal_directx_img.filepath_raw = path_texture +
normal_directx_img.name + '.png'
for i, slot in enumerate(self.object.material_slots):
mat = slot.material
nodes = get_nodes(mat)
material_output = nodes.get('Material Output')
i_node = img_nodes[i][1]
separate_node = nodes.new(type='ShaderNodeSeparateRGB')
separate_node.name = '#temp_separate'
invert_node = nodes.new(type='ShaderNodeInvert')
invert_node.name = '#temp_invert'
combine_node = nodes.new(type='ShaderNodeCombineRGB')
combine_node.name = '#temp_combine'
link(mat, i_node, i_node.outputs[0].name, separate_node,
separate_node.inputs[0].name)
link(mat, separate_node, separate_node.outputs['R'].name,
combine_node, combine_node.inputs['R'].name)
link(mat, separate_node, separate_node.outputs['B'].name,
combine_node, combine_node.inputs['B'].name)
link(mat, separate_node, separate_node.outputs['G'].name,
invert_node, invert_node.inputs['Color'].name)
link(mat, invert_node, invert_node.outputs[0].name,
combine_node, combine_node.inputs['G'].name)
link(mat, combine_node, combine_node.outputs[0].name,
material_output, 0)
selected_objects = select_objects()
deselect_objects(selected_objects)
bpy.ops.object.bake(type='COMBINED')
select_objects(selected_objects, 'SET')
try:
bpy.context.scene.render.image_settings.color_depth = '16'
bpy.context.scene.render.image_settings.file_format = 'PNG'
if not context.scene.FluentShaderProps.bake_normal_directx:
normal_img.save_render(filepath=normal_img.filepath_raw)
else:
normal_directx_img.save_render(filepath=normal_directx_img.filepath_raw)
bpy.context.scene.render.image_settings.color_depth = '8'
except:
self.report({'WARNING'}, "Blender fails to save the files on your
system. Save it manually.")
self.restore_materials_outputs()
for m in i_was_false:
m.keep_sharp = False
if context.scene.FluentShaderProps.bake_make_ao:
# créer l'image Roughness vide
ao_img = bpy.data.images.new(self.object.name+'_'+"AO",
width=self.size, height=self.size, is_data=True, float_buffer=True)
ao_img.file_format = 'PNG'
if folder_made:
ao_img.filepath_raw = path_texture+ao_img.name+'.png'
bpy.ops.object.bake(type='AO',
use_selected_to_active=self.selected_to_active, cage_extrusion=self.extrusion)
try:
ao_img.save()
except:
self.report({'WARNING'}, "Blender fails to save the files on your
system. Save it manually.")
if context.scene.FluentShaderProps.bake_make_emission:
# créer l'image Emission Color vide ###################################
emission_color_img = bpy.data.images.new(self.object.name + '_' +
"Emission_Color", width=self.size, height=self.size)
emission_color_img.file_format = 'PNG'
if folder_made:
emission_color_img.filepath_raw = path_texture +
emission_color_img.name + '.png'
bpy.ops.object.bake(type='COMBINED',
use_selected_to_active=self.selected_to_active, cage_extrusion=self.extrusion)
self.restore_materials_outputs()
try:
emission_color_img.save()
except:
self.report({'WARNING'}, "Blender fails to save the files on your
system. Save it manually.")
bpy.ops.object.bake(type='COMBINED',
use_selected_to_active=self.selected_to_active,
cage_extrusion=self.extrusion)
self.restore_materials_outputs()
try:
emission_strength_img.save()
except:
self.report({'WARNING'}, "Blender fails to save the files on your
system. Save it manually.")
img_COMBINED_node = nodes.new(type='ShaderNodeTexImage')
img_COMBINED_node.name = '#imgtemp'
img_COMBINED_node.image = combine_img
nodes.active = img_COMBINED_node
bpy.ops.object.bake(type='COMBINED')
try:
combine_img.save()
except:
self.report({'WARNING'}, "Blender fails to save the files on your
system. Save it manually.")
self.restore_materials_outputs()
bpy.context.scene.view_settings.view_transform = 'Filmic'
bpy.context.scene.render.engine = self.previous_settings['engine']
bpy.context.scene.cycles.preview_samples =
self.previous_settings['preview_samples']
bpy.context.scene.cycles.samples = self.previous_settings['render_samples']
bpy.context.space_data.shading.type =
self.previous_settings['shading_type']
bpy.context.scene.render.bake.use_selected_to_active =
self.previous_settings['baking_use_selected_to_active']
return{'FINISHED'}
def get_selected_objects_materials(self):
objects = select_objects()
materials = dict()
for object in objects :
if object != active_object():
materials[object.active_material.name] = object.active_material
return materials
if type == 'color' :
# trouve ce qui est connecté à l'entrée Color
try:
socket = PS.inputs['Base
Color'].links[0].from_socket.name
node = PS.inputs['Base Color'].links[0].from_node
except:
node = nodes.new(type='ShaderNodeRGB')
node.name = '#temp_color'
node.outputs[0].default_value = PS.inputs['Base
Color'].default_value
socket = node.outputs[0].name
# node_created = node
if type == 'roughness' :
# trouve ce qui est connecté à l'entrée Roughness
try:
socket =
PS.inputs['Roughness'].links[0].from_socket.name
node = PS.inputs['Roughness'].links[0].from_node
except:
node = nodes.new(type='ShaderNodeValue')
node.name = '#temp_roughness'
node.outputs[0].default_value =
PS.inputs['Roughness'].default_value
socket = node.outputs[0].name
node_created = node
if type == 'metallic' :
# trouve ce qui est connecté à l'entrée Metallic
try:
socket =
PS.inputs['Metallic'].links[0].from_socket.name
node = PS.inputs['Metallic'].links[0].from_node
except:
node = nodes.new(type='ShaderNodeValue')
node.name = '#temp_metallic'
node.outputs[0].default_value =
PS.inputs['Metallic'].default_value
socket = node.outputs[0].name
node_created = node
if type == 'emission' :
# trouve ce qui est connecté à l'entrée Emission
try:
socket =
PS.inputs['Emission'].links[0].from_socket.name
node = PS.inputs['Emission'].links[0].from_node
except:
node = nodes.new(type='ShaderNodeRGB')
node.name = '#temp_emission_color'
node.outputs[0].default_value =
PS.inputs['Emission'].default_value
socket = node.outputs[0].name
def restore_materials_outputs(self):
if self.highpoly_objects:
objects = self.highpoly_objects
else:
objects = [self.object]
for obj in objects:
for slot in obj.material_slots:
material = slot.material
nodes = get_nodes(material)
material_output = nodes.get('Material Output')
PS = nodes.get('Principled BSDF')
for node in nodes:
if '#temp' in node.name:
nodes.remove(node)
link(material, PS, 0, material_output, 0)
if self.selected_to_active:
self.extrusion =
context.scene.FluentShaderProps.bake_make_selected_to_active_extrusion
# Vérification
if self.object.type != 'MESH':
bpy.context.window_manager.popup_menu(
make_oops(['You try to bake a non mesh thing.']),
title="Problem detected",
icon='INFO'
)
return {'FINISHED'}
if not self.object or len(bpy.context.selected_objects) == 0:
bpy.context.window_manager.popup_menu(
make_oops(['No active object.']),
title="Problem detected",
icon='INFO'
)
return{'FINISHED'}
elif not len(self.object.data.materials):
bpy.context.window_manager.popup_menu(
make_oops(['No material for the active object.']),
title="Problem detected",
icon='INFO'
)
return {'FINISHED'}
elif not len(self.object.data.uv_layers):
bpy.context.window_manager.popup_menu(
make_oops(['No UV map detected.','Unwrap the model before to
bake.']),
title="Problem detected",
icon='INFO'
)
return {'FINISHED'}
# bake un objet
if self.object and not self.selected_to_active and not
len(bpy.context.selected_objects) >= 2:
self.bake_process(context)
elif not self.selected_to_active and len(bpy.context.selected_objects) >=
2:
bpy.context.window_manager.popup_menu(
make_oops(['No selected to active option but more than one object
selected.']),
title="Problem detected",
icon='INFO'
)
# bake high to low
if self.object and self.selected_to_active and
len(bpy.context.selected_objects) >= 2:
self.highpoly_objects = [o for o in bpy.context.selected_objects if o !
= self.object and o.type == 'MESH']
self.bake_process(context)
elif self.selected_to_active and len(bpy.context.selected_objects) < 2:
bpy.context.window_manager.popup_menu(
make_oops(['Select to active option but less than 2 objects
selected.']),
title="Problem detected",
icon='INFO'
)
return{'FINISHED'}
class FLUENT_OT_BakeMask(Operator):
"烘焙节点树的边缘和空腔遮罩\nCTRL+SHIFT+ALT 寻求帮助"
bl_idname = "fluent.bakemask"
bl_label="烘焙 PBR 贴图"
bl_options = {'REGISTER', 'UNDO'}
choice: bpy.props.StringProperty()
@classmethod
def poll(cls, context):
if active_object('GET'):
return True
else:
return False
def bake_process(self):
PS = None
obj_name = None
# trouve le principled shader
node_tree = get_node_tree(self.baked_material)
nodes = get_nodes(self.baked_material)
if self.choice == 'ALL':
mask_nodes = [node for node in nodes if 'Edges' in node.name or 'edges'
in node.name or 'Cavity' in node.name]
elif self.choice == 'EDGES':
mask_nodes = [node for node in nodes if 'Edges' in node.name or 'edges'
in node.name]
elif self.choice == 'CAVITY':
mask_nodes = [node for node in nodes if 'Cavity' in node.name]
elif self.choice == 'SELECTED':
mask_nodes = [node for node in nodes if ('Edges' in node.name or
'Cavity' in node.name or 'edges' in node.name) and node.select]
# On récupère le nom de l'objet sélectionné
obj = bpy.context.active_object
obj_name = obj.name
if mask_nodes and obj.select_get():
# sauvgarde les paramètres rendu de l'utilisateur
self.previous_settings['engine'] = bpy.context.scene.render.engine
self.previous_settings['preview_samples'] =
bpy.context.scene.cycles.preview_samples
self.previous_settings['render_samples'] =
bpy.context.scene.cycles.samples
self.previous_settings['shading_type'] =
bpy.context.space_data.shading.type
# configure le moteur de rendu pour le baking
bpy.context.scene.render.bake.margin = 2
bpy.context.scene.cycles.samples =
int(bpy.context.scene.FluentShaderProps.bake_sample)
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.view_settings.view_transform = 'Standard'
try:
file_path = bpy.data.filepath
file_name = file_path.split('\\')[-1]
path = file_path.split(file_name)[0]
path_texture = path+'Textures\\'
if not os.path.exists(path_texture):
os.makedirs(path_texture)
folder_made = True
except:
folder_made = False
self.report({'INFO'}, "The blend file must be saved for automatic
image saving.")
for mask_node in mask_nodes:
# mute le mask mixer
for n in mask_node.node_tree.nodes:
if n.bl_idname == 'ShaderNodeGroup' and 'Mask Texture Mixer' in
n.node_tree.name:
n.mute = True
nodes.remove(img_node)
nodes.remove(emission_node)
bpy.context.scene.view_settings.view_transform = 'Filmic'
bpy.context.scene.render.engine = self.previous_settings['engine']
bpy.context.scene.cycles.preview_samples =
self.previous_settings['preview_samples']
bpy.context.scene.cycles.samples =
self.previous_settings['render_samples']
bpy.context.space_data.shading.type =
self.previous_settings['shading_type']
else:
self.report({'ERROR'}, 'No selected object.')
return{'FINISHED'}
if not len(active_object('GET').data.uv_layers):
bpy.context.window_manager.popup_menu(
make_oops(['No UV map detected.','Unwrap the model before to
bake.']),
title="Problem detected",
icon='INFO'
)
return {'FINISHED'}
self.bake_process()
return{'FINISHED'}
#####################################################
class FluentShaderProps(bpy.types.PropertyGroup):
show_bake_setting: bpy.props.BoolProperty(
default=False
)
show_bake_pbr: bpy.props.BoolProperty(
default=False
)
show_bake_mask: bpy.props.BoolProperty(
default=False
)
show_bake_decal: bpy.props.BoolProperty(
default=False
)
map_size : bpy.props.EnumProperty(
name='贴图大小',
items=(
("1024", "1024", "1024"),
("2048", "2048", "2048"),
("4096", "4096", "4096"),
("8192", "8192", "8192")
),
default='2048'
)
bake_sample : bpy.props.EnumProperty(
name='Sample',
items=(
("1", "1", "1"),
("2", "2", "2"),
("4", "4", "4"),
("8", "8", "8"),
("16", "16", "16"),
("32", "32", "32"),
("64", "64", "64")
),
default='16'
)
sections : bpy.props.EnumProperty(
name='分类',
items=(
("imperfections", "瑕疵", "imperfections"),
("grunges", "垃圾", "grunges"),
("patterns", "Patterns", "patterns"),
("liquid", "Liquid", "liquid"),
("city", "城市", "city"),
("normals", "Normals", "normals"),
("fabric", "织物", "fabric"),
("metal", "Metal", "metal"),
("shaders", "着色器", "shaders"),
("wood", "Wood", "wood"),
("screen", "Screen", "screen"),
),
default='imperfections'
)
scale_scale : bpy.props.FloatProperty(
description = "乘以默认添加的节点比例.用于自动调整对象大小",
name = "Scale",
default = 1,
min = 0,
step = 0.01,
precision = 2
)
bake_make_color : BoolProperty(
default = True,
name = '颜色',
description = "烘焙颜色贴图"
)
bake_make_metallic : BoolProperty(
default = True,
name = '金属度',
description = "烘焙金属贴图"
)
bake_make_roughness : BoolProperty(
default = True,
name = '粗糙度',
description = "烘焙粗糙度贴图"
)
bake_make_normal : BoolProperty(
default = True,
name = '法线',
description = "烘焙法线贴图"
)
bake_make_emission : BoolProperty(
default = False,
name = '自发光(发射)',
description = "烘焙自发光(发射)贴图"
)
bake_make_ao : BoolProperty(
default = False,
name = 'AO',
description = "烘焙 AO 环境遮挡贴图"
)
bake_make_alpha : BoolProperty(
default = False,
name = 'Alpha',
description = "烘焙 alpha 贴图"
)
bake_normal_opengl : BoolProperty(
default = True,
name = 'OpenGL',
description = "保存 opengl 法线贴图"
)
bake_normal_directx : BoolProperty(
default = False,
name = 'DirectX',
description = "保存 DirectX 法线贴图"
)
bake_make_selected_to_active : BoolProperty(
default = False,
name = '将高多边形烘焙成低多边形',
description = "将一个或多个对象烘焙为一个\n 警告:您需要选择所有要烘焙的对象并保持目标
对象处于活动状态"
)
bake_make_selected_to_active_extrusion : FloatProperty(
default = 0.01,
name = '挤出',
description = "用于在选择到活动的烘焙模式下烘焙的挤出.\n 注意:此设置将取决于您的对象的
大小.如果您得到奇怪的结果,请降低它"
)
global preview_collections
f_imperfections : bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["imperfections"]),
update = import_imperfections
)
f_grunges : bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["grunges"]),
update = import_grunges
)
f_patterns : bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["patterns"]),
update = import_patterns
)
f_liquid : bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["liquid"]),
update = import_liquid
)
f_city : bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["city"]),
update = import_city
)
f_normals : bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["normals"]),
update = import_normals
)
f_fabric : bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["fabric"]),
update = import_fabric
)
f_metal : bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["metal"]),
update = import_metal
)
f_shaders : bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["shaders"]),
update = import_shaders
)
f_wood: bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["wood"]),
update=import_wood
)
f_screen: bpy.props.EnumProperty(
items=enum_previews_from_directory_items(preview_collections["screen"]),
update=import_screen
)
#####################################################
class FLUENT_SHADER_MT_Pie_Menu(bpy.types.Menu):
bl_label = 'Fluent MZ '+str(bl_info['version'][0])+'.'+str(bl_info['version']
[1])+'.'+str(bl_info['version'][2])
bl_options = {'REGISTER'}
@classmethod
def poll(cls, context):
return (bpy.context.area.ui_type == 'ShaderNodeTree')
layout = self.layout
pie = layout.menu_pie()
LEFT = pie.column()
LEFT.label(text='图层')
box = LEFT.column()
box.scale_x = 1.2
box.scale_y = 1.5
box.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="混合图层").choice =
"Mix Layers"
row = box.row(align=True)
row.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="新建图层").choice =
"Layer"
row.operator(FLUENT_SHADER_OT_SwapLayers.bl_idname, text="",
icon="FILE_REFRESH")
row.operator(FLUENT_SHADER_OT_LayerMixLayersConnect.bl_idname, text="",
icon="LINKED")
row = LEFT.row(align=True)
row.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="2 layers").choice =
"Smart Shader 2 layers"
row.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="3 layers").choice =
"Smart Shader 3 layers"
RIGHT = pie.column()
RIGHT.label(text='蒙版')
row = RIGHT.row(align=True)
row.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="所有边缘").choice =
"All edges"
row.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="边缘").choice =
"Edges"
RIGHT.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="凹洞").choice =
"Cavity"
row = RIGHT.row(align=True)
row.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="定向",
icon="EMPTY_SINGLE_ARROW").choice = "Directional Mask"
row.operator(FLUENT_SHADER_OT_Localmask.bl_idname, text="局部")
row = RIGHT.row(align=True)
row.operator(FLUENT_SHADER_OT_NewPaintedMask.bl_idname, text="绘制",
icon="BRUSH_DATA").option = "Painted Mask"
row.operator(FLUENT_SHADER_OT_NewPaintedMask.bl_idname, text="",
icon_value=multiply_ico.icon_id).option = 'MULTIPLY'
row.operator(FLUENT_SHADER_OT_NewPaintedMask.bl_idname, text="",
icon_value=difference_ico.icon_id).option = 'DIFFERENCE'
row.operator(FLUENT_SHADER_OT_EditPaintedMask.bl_idname, text="",
icon="GP_MULTIFRAME_EDITING")
RIGHT.label(text='贴花')
row = RIGHT.row(align=True)
row.operator(FLUENT_SHADER_OT_OpenFilebrowser.bl_idname, text='添加',
icon='PLUS').continue_to = 'new_decal'
row.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text='混合器',
icon='SELECT_EXTEND').choice = 'Decal Mixer'
row.operator(FLUENT_SHADER_OT_NewDecal.bl_idname, text='复制',
icon='DUPLICATE').duplicate = True
BOTTOM = pie.column()
BOTTOM.label(text='数学')
colonneA = BOTTOM.row(align=True)
colonneA.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="屏幕").choice
= "Screen"
colonneA.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="变亮").choice
= "Lighten"
colonneB = BOTTOM.row(align=True)
colonneB.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="覆盖").choice
= "Overlay"
colonneB.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="差异").choice
= "Difference"
colonneC = BOTTOM.row(align=True)
colonneC.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="相乘").choice
= "Multiply"
colonneC.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="数学混
合").choice = "Math Mix"
colonneD = BOTTOM.row(align=True)
colonneD.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="移位/锐
化").choice = "Shift/Sharpen"
colonneD.operator(FLUENT_SHADER_OT_addNodes.bl_idname, text="反转").choice
= "Invert"
pie.menu("FLUENT_MT_Color_Menu")
class FLUENT_MT_Color_Menu(Menu):
bl_label = "金属颜色"
class FLUENT_SHADER_OT_NodeCounter(Operator):
bl_idname = 'fluent.node_counter'
bl_description = '计算节点树中的节点'
bl_category = 'Node'
bl_label = '节点计数器'
class FLUENT_SHADER_OT_ImageExtractor(Operator):
bl_idname = 'fluent.imageextractor'
bl_description = '将图像转换为组以提取粗糙度和法线'
bl_category = 'Node'
bl_label = '图像提取器'
def execute(self, context):
active_material = active_object('GET').active_material
nodes = get_nodes(active_material)
class FLUENT_SHADER_PT_Tool(bpy.types.Panel):
"Fluent Shader Panel"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = 'Fluent'
bl_label = '工具'
@classmethod
def poll(cls, context):
if (context.space_data.tree_type == "ShaderNodeTree" and
context.space_data.shader_type == "OBJECT"):
return True
else:
return False
class FLUENT_SHADER_OT_SwapLayers(Operator):
"""交换所选混合图层的所有条目"""
bl_idname = 'fluent.swaplayers'
bl_category = 'Node'
bl_label = '交换图层'
if len(bpy.context.selected_nodes) == 2:
if not 'Mix' in bpy.context.selected_nodes[0].name and not 'Mix' in
bpy.context.selected_nodes[1].name:
if 'Layer' in bpy.context.selected_nodes[0].name and 'Layer' in
bpy.context.selected_nodes[1].name:
layer_1 = bpy.context.selected_nodes[0]
layer_2 = bpy.context.selected_nodes[1]
else:
bpy.context.window_manager.popup_menu(make_oops(['Select two Layers
node.']), title="INFO", icon='INFO')
return{'FINISHED'}
try:
link(active_material, layer_1, layer_1.outputs['Emission
Strength'].name, emission_2['node'][0], emission_2['socket'][0])
link(active_material, layer_2, layer_2.outputs['Emission
Strength'].name, emission_1['node'][0], emission_1['socket'][0])
except:pass
return{'FINISHED'}
class FLUENT_SHADER_OT_LayerMixLayersConnect(Operator):
"""将图层连接到混合图层"""
bl_idname = 'fluent.layermixlayersconnect'
bl_category = 'Node'
bl_label = '图层连接器'
selected_nodes = get_selected_nodes(nodes)
layer_node = None
mix_node = None
if len(selected_nodes) == 2:
if selected_nodes[0].type == 'GROUP' and
selected_nodes[0].node_tree.name == 'Layer':
layer_node = selected_nodes[0]
elif selected_nodes[1].type == 'GROUP' and
selected_nodes[1].node_tree.name == 'Layer':
layer_node = selected_nodes[1]
if selected_nodes[0].type == 'GROUP' and
selected_nodes[0].node_tree.name == 'Mix Layers':
mix_node = selected_nodes[0]
elif selected_nodes[1].type == 'GROUP' and
selected_nodes[1].node_tree.name == 'Mix Layers':
mix_node = selected_nodes[1]
return{'FINISHED'}
class FLUENT_SHADER_OT_NewPaintedMask(Operator):
"""绘制蒙版"""
bl_idname = 'fluent.newpaintedmask'
bl_category = 'Node'
bl_label = '绘制蒙版'
size : IntProperty(
name="Size",
description="Image size.",
default=4096
)
the_node : StringProperty()
option : StringProperty()
class FLUENT_SHADER_OT_EditPaintedMask(Operator):
"""编辑选择的绘制蒙版"""
bl_idname = 'fluent.editpaintedmask'
bl_category = 'Node'
bl_label = '编辑已绘制的蒙版'
return{'FINISHED'}
class FLUENT_SHADER_OT_Refresh(Operator):
"""编辑选择的绘制蒙版"""
bl_idname = 'fluent.refresh'
bl_label = '刷新 Cycles 视口'
class FLUENT_SHADER_OT_NewDecal(Operator):
"""添加贴花节点"""
bl_idname = 'fluent.newdecal'
bl_label = '添加贴花节点'
image_path: bpy.props.StringProperty()
duplicate: bpy.props.BoolProperty()
global temp
decal_node = temp['last_addition']
original = decal_node.node_tree
single = original.copy()
decal_node.node_tree = single
bpy.ops.object.empty_add(type='SINGLE_ARROW', align='WORLD')
empty = active_object(action='GET')
empty['fluent_type'] = 'decal_projector'
if 'previous_empty' in locals():
empty.scale = previous_empty.scale
empty.rotation_euler = previous_empty.rotation_euler
empty.location = previous_empty.location
if not decal_image:
decal_image = bpy.data.images.load(filepath=self.image_path,
check_existing=False)
decal_node.node_tree.nodes['Image Texture'].image = decal_image
if not self.duplicate:
ratio = decal_image.size[1]/decal_image.size[0]
empty.scale[0] = 1/ratio
return{'FINISHED'}
class FLUENT_SHADER_OT_Localmask(Operator):
"""添加本地蒙版节点"""
bl_idname = 'fluent.localmask'
bl_label = '添加本地蒙版节点'
global temp
decal_node = temp['last_addition']
original = decal_node.node_tree
single = original.copy()
decal_node.node_tree = single
bpy.ops.object.empty_add(type='SINGLE_ARROW', align='WORLD')
empty = active_object(action='GET')
empty['fluent_type'] = 'decal_projector'
return{'FINISHED'}
#################################################################################
class FluentAddonPreferences(AddonPreferences):
bl_idname = __name__
class AddonKeymaps:
_addon_keymaps = []
_keymaps = {}
@classmethod
def new_keymap(cls, name, kmi_name, kmi_value=None, km_name='3D View',
space_type="VIEW_3D", region_type="WINDOW",
event_type=None, event_value=None, ctrl=False, shift=False,
alt=False, key_modifier="NONE"):
"""
Adds a new keymap
:param name: str, Name that will be displayed in the panel preferences
:param kmi_name: str
- bl_idname for the operators (exemple: 'object.cube_add')
- 'wm.call_menu' for menu
- 'wm.call_menu_pie' for pie menu
:param kmi_value: str
- class name for Menu or Pie Menu
- None for operators
:param km_name: str, keymap name (exemple: '3D View Generic')
:param space_type: str, space type keymap is associated with, see:
https://docs.blender.org/api/current/bpy.types.KeyMap.html?
highlight=space_type#bpy.types.KeyMap.space_type
:param region_type: str, region type keymap is associated with, see:
https://docs.blender.org/api/current/bpy.types.KeyMap.html?
highlight=region_type#bpy.types.KeyMap.region_type
:param event_type: str, see:
https://docs.blender.org/api/current/bpy.types.Event.html?
highlight=event#bpy.types.Event.type
:param event_value: str, type of the event, see:
https://docs.blender.org/api/current/bpy.types.Event.html?
highlight=event#bpy.types.Event.value
:param ctrl: bool
:param shift: bool
:param alt: bool
:param key_modifier: str, regular key pressed as a modifier
https://docs.blender.org/api/current/bpy.types.KeyMapItem.html?
highlight=modifier#bpy.types.KeyMapItem.key_modifier
:return:
"""
cls._keymaps.update({name: [kmi_name, kmi_value, km_name, space_type,
region_type, event_type, event_value,
ctrl, shift, alt, key_modifier]
})
@classmethod
def add_hotkey(cls, kc, keymap_name):
items = cls._keymaps.get(keymap_name)
if not items:
return
kmi.active = True
cls._addon_keymaps.append((km, kmi))
@staticmethod
def register_keymaps():
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
# In background mode, there's no such thing has keyconfigs.user,
# because headless mode doesn't need key combos.
# So, to avoid error message in background mode, we need to check if
# keyconfigs is loaded.
if not kc:
return
@classmethod
def unregister_keymaps(cls):
kmi_values = [item[1] for item in cls._keymaps.values() if item]
kmi_names = [item[0] for item in cls._keymaps.values() if
item not in ['wm.call_menu', 'wm.call_menu_pie']]
cls._addon_keymaps.clear()
@staticmethod
def get_hotkey_entry_item(name, kc, km, kmi_name, kmi_value, col):
# for operators
else:
if km.keymap_items.get(kmi_name):
col.context_pointer_set('keymap', km)
rna_keymap_ui.draw_kmi([], kc, km, km.keymap_items[kmi_name],
col, 0)
else:
col.label(text=f"No hotkey entry found for {name}")
col.operator(TEMPLATE_OT_restore_hotkey.bl_idname,
text="恢复键盘映射",
icon='ADD').km_name = km.name
@staticmethod
def draw_keymap_items(wm, layout):
kc = wm.keyconfigs.user
box = layout.box()
for name, items in AddonKeymaps._keymaps.items():
kmi_name, kmi_value, km_name = items[:3]
split = box.split()
col = split.column()
# col.label(text=name)
# col.separator()
km = kc.keymaps[km_name]
AddonKeymaps.get_hotkey_entry_item(name, kc, km, kmi_name,
kmi_value, col)
class TEMPLATE_OT_restore_hotkey(Operator):
bl_idname = "template.restore_hotkey"
bl_label = "恢复热键"
bl_options = {'REGISTER', 'INTERNAL'}
km_name: StringProperty()
class FLUENT_SHADER_OT_FindHelp(Operator):
"""如何寻求帮助"""
bl_idname = 'fluent.findhelp'
bl_label = '如何寻求帮助'
filter_glob: StringProperty(
default='*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp',
options={'HIDDEN'}
)
continue_to: StringProperty(
default='',
options={'HIDDEN'}
)
# some_boolean: BoolProperty(
# name='Do a thing',
# description='Do a thing with the file you\'ve selected',
# default=True,
# )
if self.continue_to == 'new_decal':
bpy.ops.fluent.newdecal('INVOKE_DEFAULT', image_path=self.filepath)
return {'FINISHED'}
classes = (
FluentShaderProps,
FLUENT_SHADER_PT_ViewPanel,
FLUENT_SHADER_PT_Library,
FLUENT_SHADER_MT_Pie_Menu,
FLUENT_MT_Color_Menu,
FLUENT_SHADER_OT_addNodes,
FLUENT_OT_BakeMaps,
FLUENT_OT_BakeMask,
FLUENT_SHADER_OT_NodeCounter,
FLUENT_SHADER_OT_ImageExtractor,
FLUENT_SHADER_PT_Tool,
FLUENT_SHADER_OT_SwapLayers,
FLUENT_SHADER_OT_LayerMixLayersConnect,
FLUENT_SHADER_OT_NewPaintedMask,
FLUENT_SHADER_OT_EditPaintedMask,
FLUENT_SHADER_OT_NewDecal,
FLUENT_SHADER_OT_Localmask,
FLUENT_SHADER_OT_Refresh,
FLUENT_SHADER_OT_FindHelp,
FLUENT_SHADER_OT_OpenFilebrowser,
FluentAddonPreferences
)
def register():
global preview_collections
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
AddonKeymaps.unregister_keymaps()
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
if __name__ == "__main__":
register()