Speed up FBX export of polygon indices and edges with numpy #104451
@ -11,7 +11,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Extra Objects",
|
"name": "Extra Objects",
|
||||||
"author": "Multiple Authors",
|
"author": "Multiple Authors",
|
||||||
"version": (0, 3, 8),
|
"version": (0, 3, 9),
|
||||||
"blender": (2, 80, 0),
|
"blender": (2, 80, 0),
|
||||||
"location": "View3D > Add > Mesh",
|
"location": "View3D > Add > Mesh",
|
||||||
"description": "Add extra mesh object types",
|
"description": "Add extra mesh object types",
|
||||||
|
@ -64,9 +64,7 @@ def Add_Symmetrical_Empty():
|
|||||||
sempty.name = "SymmEmpty"
|
sempty.name = "SymmEmpty"
|
||||||
|
|
||||||
# check if we have a mirror modifier, otherwise add
|
# check if we have a mirror modifier, otherwise add
|
||||||
if (sempty.modifiers and sempty.modifiers['Mirror']):
|
if not any(mod.type == 'MIRROR' for mod in sempty.modifiers):
|
||||||
pass
|
|
||||||
else:
|
|
||||||
bpy.ops.object.modifier_add(type='MIRROR')
|
bpy.ops.object.modifier_add(type='MIRROR')
|
||||||
|
|
||||||
# Delete all!
|
# Delete all!
|
||||||
@ -83,9 +81,7 @@ def Add_Symmetrical_Vert():
|
|||||||
sempty.name = "SymmVert"
|
sempty.name = "SymmVert"
|
||||||
|
|
||||||
# check if we have a mirror modifier, otherwise add
|
# check if we have a mirror modifier, otherwise add
|
||||||
if (sempty.modifiers and sempty.modifiers['Mirror']):
|
if not any(mod.type == 'MIRROR' for mod in sempty.modifiers):
|
||||||
pass
|
|
||||||
else:
|
|
||||||
bpy.ops.object.modifier_add(type='MIRROR')
|
bpy.ops.object.modifier_add(type='MIRROR')
|
||||||
|
|
||||||
# Delete all!
|
# Delete all!
|
||||||
@ -102,7 +98,8 @@ class AddSymmetricalEmpty(Operator):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
mirror = bpy.context.object.modifiers['Mirror']
|
mirror = next(mod for mod in bpy.context.object.modifiers
|
||||||
|
if mod.type == 'MIRROR')
|
||||||
|
|
||||||
layout.prop(mirror, "use_clip", text="Use Clipping")
|
layout.prop(mirror, "use_clip", text="Use Clipping")
|
||||||
|
|
||||||
@ -126,7 +123,8 @@ class AddSymmetricalVert(Operator):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
mirror = bpy.context.object.modifiers['Mirror']
|
mirror = next(mod for mod in bpy.context.object.modifiers
|
||||||
|
if mod.type == 'MIRROR')
|
||||||
|
|
||||||
layout.prop(mirror, "use_clip", text="Use Clipping")
|
layout.prop(mirror, "use_clip", text="Use Clipping")
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ bl_info = {
|
|||||||
"name": "Archimesh",
|
"name": "Archimesh",
|
||||||
"author": "Antonio Vazquez (antonioya)",
|
"author": "Antonio Vazquez (antonioya)",
|
||||||
"location": "View3D > Add Mesh / Sidebar > Create Tab",
|
"location": "View3D > Add Mesh / Sidebar > Create Tab",
|
||||||
"version": (1, 2, 4),
|
"version": (1, 2, 5),
|
||||||
"blender": (3, 0, 0),
|
"blender": (3, 0, 0),
|
||||||
"description": "Generate rooms, doors, windows, and other architecture objects",
|
"description": "Generate rooms, doors, windows, and other architecture objects",
|
||||||
"doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/archimesh.html",
|
"doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/archimesh.html",
|
||||||
|
@ -108,8 +108,7 @@ class ARCHIMESH_OT_Hole(Operator):
|
|||||||
# ---------------------------------------
|
# ---------------------------------------
|
||||||
for child in obj.parent.children:
|
for child in obj.parent.children:
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
if "archimesh.ctrl_hole" in child and child["archimesh.ctrl_hole"]:
|
||||||
if child["archimesh.ctrl_hole"]:
|
|
||||||
# apply scale
|
# apply scale
|
||||||
t = parentobj.RoomGenerator[0].wall_width
|
t = parentobj.RoomGenerator[0].wall_width
|
||||||
if t > 0:
|
if t > 0:
|
||||||
@ -117,11 +116,8 @@ class ARCHIMESH_OT_Hole(Operator):
|
|||||||
else:
|
else:
|
||||||
child.scale.y = 1
|
child.scale.y = 1
|
||||||
# add boolean modifier
|
# add boolean modifier
|
||||||
if isboolean(myroom, child) is False:
|
if not isboolean(myroom, child):
|
||||||
set_modifier_boolean(myroom, child)
|
set_modifier_boolean(myroom, child)
|
||||||
except:
|
|
||||||
# print("Unexpected error:" + str(sys.exc_info()))
|
|
||||||
pass
|
|
||||||
|
|
||||||
# ---------------------------------------
|
# ---------------------------------------
|
||||||
# Now add the modifiers to baseboard
|
# Now add the modifiers to baseboard
|
||||||
|
@ -172,9 +172,7 @@ def set_modifier_solidify(myobject, width):
|
|||||||
# Add modifier (boolean)
|
# Add modifier (boolean)
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
def set_modifier_boolean(myobject, bolobject):
|
def set_modifier_boolean(myobject, bolobject):
|
||||||
bpy.context.view_layer.objects.active = myobject
|
boolean_modifier = myobject.modifiers.new("", 'BOOLEAN')
|
||||||
if bpy.context.view_layer.objects.active.name == myobject.name:
|
|
||||||
boolean_modifier = context.object.modifiers.new("", 'BOOLEAN')
|
|
||||||
boolean_modifier.operation = 'DIFFERENCE'
|
boolean_modifier.operation = 'DIFFERENCE'
|
||||||
boolean_modifier.object = bolobject
|
boolean_modifier.object = bolobject
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ from .fbx_utils import (
|
|||||||
units_blender_to_fbx_factor, units_convertor, units_convertor_iter,
|
units_blender_to_fbx_factor, units_convertor, units_convertor_iter,
|
||||||
matrix4_to_array, similar_values, similar_values_iter, astype_view_signedness, fast_first_axis_unique,
|
matrix4_to_array, similar_values, similar_values_iter, astype_view_signedness, fast_first_axis_unique,
|
||||||
# Mesh transform helpers.
|
# Mesh transform helpers.
|
||||||
vcos_transformed_gen, nors_transformed_gen, vcos_transformed, nors_transformed,
|
vcos_transformed_gen, vcos_transformed, nors_transformed,
|
||||||
# UUID from key.
|
# UUID from key.
|
||||||
get_fbx_uuid_from_key,
|
get_fbx_uuid_from_key,
|
||||||
# Key generators.
|
# Key generators.
|
||||||
@ -1140,28 +1140,35 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||||||
# but this does not seem well supported by apps currently...
|
# but this does not seem well supported by apps currently...
|
||||||
me.calc_normals_split()
|
me.calc_normals_split()
|
||||||
|
|
||||||
t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 3
|
ln_bl_dtype = np.single
|
||||||
|
ln_fbx_dtype = np.float64
|
||||||
|
t_ln = np.empty(len(me.loops) * 3, dtype=ln_bl_dtype)
|
||||||
me.loops.foreach_get("normal", t_ln)
|
me.loops.foreach_get("normal", t_ln)
|
||||||
t_ln = nors_transformed_gen(t_ln, geom_mat_no)
|
t_ln = nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype)
|
||||||
if 0:
|
if 0:
|
||||||
t_ln = tuple(t_ln) # No choice... :/
|
lnidx_fbx_dtype = np.int32
|
||||||
|
|
||||||
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
|
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
|
||||||
elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
|
elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
|
||||||
elem_data_single_string(lay_nor, b"Name", b"")
|
elem_data_single_string(lay_nor, b"Name", b"")
|
||||||
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
||||||
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect")
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect")
|
||||||
|
|
||||||
ln2idx = tuple(set(t_ln))
|
# Tuple of unique sorted normals and then the index in the unique sorted normals of each normal in t_ln.
|
||||||
elem_data_single_float64_array(lay_nor, b"Normals", chain(*ln2idx))
|
# Since we don't care about how the normals are sorted, only that they're unique, we can use the fast unique
|
||||||
|
# helper function.
|
||||||
|
t_ln, t_lnidx = fast_first_axis_unique(t_ln.reshape(-1, 3), return_inverse=True)
|
||||||
|
|
||||||
|
# Convert to the type for fbx
|
||||||
|
t_lnidx = astype_view_signedness(t_lnidx, lnidx_fbx_dtype)
|
||||||
|
|
||||||
|
elem_data_single_float64_array(lay_nor, b"Normals", t_ln)
|
||||||
# Normal weights, no idea what it is.
|
# Normal weights, no idea what it is.
|
||||||
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(ln2idx)
|
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(t_ln)
|
||||||
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw)
|
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw)
|
||||||
|
|
||||||
ln2idx = {nor: idx for idx, nor in enumerate(ln2idx)}
|
elem_data_single_int32_array(lay_nor, b"NormalsIndex", t_lnidx)
|
||||||
elem_data_single_int32_array(lay_nor, b"NormalsIndex", (ln2idx[n] for n in t_ln))
|
|
||||||
|
|
||||||
del ln2idx
|
del t_lnidx
|
||||||
# del t_lnw
|
# del t_lnw
|
||||||
else:
|
else:
|
||||||
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
|
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
|
||||||
@ -1169,7 +1176,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||||||
elem_data_single_string(lay_nor, b"Name", b"")
|
elem_data_single_string(lay_nor, b"Name", b"")
|
||||||
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
||||||
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
||||||
elem_data_single_float64_array(lay_nor, b"Normals", chain(*t_ln))
|
elem_data_single_float64_array(lay_nor, b"Normals", t_ln)
|
||||||
# Normal weights, no idea what it is.
|
# Normal weights, no idea what it is.
|
||||||
# t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
|
# t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
|
||||||
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln)
|
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln)
|
||||||
@ -1180,9 +1187,10 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||||||
tspacenumber = len(me.uv_layers)
|
tspacenumber = len(me.uv_layers)
|
||||||
if tspacenumber:
|
if tspacenumber:
|
||||||
# We can only compute tspace on tessellated meshes, need to check that here...
|
# We can only compute tspace on tessellated meshes, need to check that here...
|
||||||
t_lt = [None] * len(me.polygons)
|
lt_bl_dtype = np.uintc
|
||||||
|
t_lt = np.empty(len(me.polygons), dtype=lt_bl_dtype)
|
||||||
me.polygons.foreach_get("loop_total", t_lt)
|
me.polygons.foreach_get("loop_total", t_lt)
|
||||||
if any((lt > 4 for lt in t_lt)):
|
if (t_lt > 4).any():
|
||||||
del t_lt
|
del t_lt
|
||||||
scene_data.settings.report(
|
scene_data.settings.report(
|
||||||
{'WARNING'},
|
{'WARNING'},
|
||||||
@ -1191,7 +1199,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||||||
else:
|
else:
|
||||||
del t_lt
|
del t_lt
|
||||||
num_loops = len(me.loops)
|
num_loops = len(me.loops)
|
||||||
t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * num_loops * 3
|
t_ln = np.empty(num_loops * 3, dtype=ln_bl_dtype)
|
||||||
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
|
# t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
|
||||||
uv_names = [uvlayer.name for uvlayer in me.uv_layers]
|
uv_names = [uvlayer.name for uvlayer in me.uv_layers]
|
||||||
# Annoying, `me.calc_tangent` errors in case there is no geometry...
|
# Annoying, `me.calc_tangent` errors in case there is no geometry...
|
||||||
@ -1209,7 +1217,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||||||
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
||||||
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
||||||
elem_data_single_float64_array(lay_nor, b"Binormals",
|
elem_data_single_float64_array(lay_nor, b"Binormals",
|
||||||
chain(*nors_transformed_gen(t_ln, geom_mat_no)))
|
nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype))
|
||||||
# Binormal weights, no idea what it is.
|
# Binormal weights, no idea what it is.
|
||||||
# elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
|
# elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
|
||||||
|
|
||||||
@ -1222,7 +1230,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||||||
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
|
||||||
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
|
||||||
elem_data_single_float64_array(lay_nor, b"Tangents",
|
elem_data_single_float64_array(lay_nor, b"Tangents",
|
||||||
chain(*nors_transformed_gen(t_ln, geom_mat_no)))
|
nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype))
|
||||||
# Tangent weights, no idea what it is.
|
# Tangent weights, no idea what it is.
|
||||||
# elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)
|
# elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)
|
||||||
|
|
||||||
@ -1312,16 +1320,27 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||||||
elem_data_single_string(lay_ma, b"Name", b"")
|
elem_data_single_string(lay_ma, b"Name", b"")
|
||||||
nbr_mats = len(me_fbxmaterials_idx)
|
nbr_mats = len(me_fbxmaterials_idx)
|
||||||
if nbr_mats > 1:
|
if nbr_mats > 1:
|
||||||
t_pm = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons)
|
bl_pm_dtype = np.uintc
|
||||||
|
fbx_pm_dtype = np.int32
|
||||||
|
t_pm = np.empty(len(me.polygons), dtype=bl_pm_dtype)
|
||||||
me.polygons.foreach_get("material_index", t_pm)
|
me.polygons.foreach_get("material_index", t_pm)
|
||||||
|
|
||||||
# We have to validate mat indices, and map them to FBX indices.
|
# We have to validate mat indices, and map them to FBX indices.
|
||||||
# Note a mat might not be in me_fbxmats_idx (e.g. node mats are ignored).
|
# Note a mat might not be in me_fbxmaterials_idx (e.g. node mats are ignored).
|
||||||
def_ma = next(me_fbxmaterials_idx[m] for m in me_blmaterials if m in me_fbxmaterials_idx)
|
|
||||||
blmaterials_to_fbxmaterials_idxs = [me_fbxmaterials_idx.get(m, def_ma) for m in me_blmaterials]
|
# The first valid material will be used for materials out of bounds of me_blmaterials or materials not
|
||||||
ma_idx_limit = len(blmaterials_to_fbxmaterials_idxs)
|
# in me_fbxmaterials_idx.
|
||||||
_gen = (blmaterials_to_fbxmaterials_idxs[m] if m < ma_idx_limit else def_ma for m in t_pm)
|
def_me_blmaterial_idx, def_ma = next(
|
||||||
t_pm = array.array(data_types.ARRAY_INT32, _gen)
|
(i, me_fbxmaterials_idx[m]) for i, m in enumerate(me_blmaterials) if m in me_fbxmaterials_idx)
|
||||||
|
|
||||||
|
# Set material indices that are out of bounds to the default material index
|
||||||
|
mat_idx_limit = len(me_blmaterials)
|
||||||
|
t_pm[t_pm >= mat_idx_limit] = def_me_blmaterial_idx
|
||||||
|
|
||||||
|
# Map to FBX indices. Materials not in me_fbxmaterials_idx will be set to the default material index.
|
||||||
|
blmat_fbx_idx = np.fromiter((me_fbxmaterials_idx.get(m, def_ma) for m in me_blmaterials),
|
||||||
|
dtype=fbx_pm_dtype)
|
||||||
|
t_pm = blmat_fbx_idx[t_pm]
|
||||||
|
|
||||||
elem_data_single_string(lay_ma, b"MappingInformationType", b"ByPolygon")
|
elem_data_single_string(lay_ma, b"MappingInformationType", b"ByPolygon")
|
||||||
# XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
|
# XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
|
||||||
|
@ -265,13 +265,6 @@ def vcos_transformed_gen(raw_cos, m=None):
|
|||||||
gen = zip(*(iter(raw_cos),) * 3)
|
gen = zip(*(iter(raw_cos),) * 3)
|
||||||
return gen if m is None else (m @ Vector(v) for v in gen)
|
return gen if m is None else (m @ Vector(v) for v in gen)
|
||||||
|
|
||||||
def nors_transformed_gen(raw_nors, m=None):
|
|
||||||
# Great, now normals are also expected 4D!
|
|
||||||
# XXX Back to 3D normals for now!
|
|
||||||
# gen = zip(*(iter(raw_nors),) * 3 + (_infinite_gen(1.0),))
|
|
||||||
gen = zip(*(iter(raw_nors),) * 3)
|
|
||||||
return gen if m is None else (m @ Vector(v) for v in gen)
|
|
||||||
|
|
||||||
|
|
||||||
def _mat4_vec3_array_multiply(mat4, vec3_array, dtype=None, return_4d=False):
|
def _mat4_vec3_array_multiply(mat4, vec3_array, dtype=None, return_4d=False):
|
||||||
"""Multiply a 4d matrix by each 3d vector in an array and return as an array of either 3d or 4d vectors.
|
"""Multiply a 4d matrix by each 3d vector in an array and return as an array of either 3d or 4d vectors.
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
'name': 'glTF 2.0 format',
|
'name': 'glTF 2.0 format',
|
||||||
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
||||||
"version": (3, 6, 4),
|
"version": (3, 6, 5),
|
||||||
'blender': (3, 5, 0),
|
'blender': (3, 5, 0),
|
||||||
'location': 'File > Import-Export',
|
'location': 'File > Import-Export',
|
||||||
'description': 'Import-Export as glTF 2.0',
|
'description': 'Import-Export as glTF 2.0',
|
||||||
|
@ -137,5 +137,5 @@ def __convert_keyframes(obj_uuid: str, channel: str, keyframes, action_name: str
|
|||||||
return input, output
|
return input, output
|
||||||
|
|
||||||
def __gather_interpolation(export_settings):
|
def __gather_interpolation(export_settings):
|
||||||
# TODO: check if the bone was animated with CONSTANT
|
# TODO: check if the object was animated with CONSTANT
|
||||||
return 'LINEAR'
|
return 'LINEAR'
|
||||||
|
@ -103,5 +103,5 @@ def __convert_keyframes(obj_uuid, keyframes, action_name: str, export_settings):
|
|||||||
return input, output
|
return input, output
|
||||||
|
|
||||||
def __gather_interpolation(export_settings):
|
def __gather_interpolation(export_settings):
|
||||||
# TODO: check if the bone was animated with CONSTANT
|
# TODO: check if the SK was animated with CONSTANT
|
||||||
return 'LINEAR'
|
return 'LINEAR'
|
||||||
|
@ -5,6 +5,7 @@ import numpy as np
|
|||||||
|
|
||||||
from ...io.com import gltf2_io, gltf2_io_constants, gltf2_io_debug
|
from ...io.com import gltf2_io, gltf2_io_constants, gltf2_io_debug
|
||||||
from ...io.exp import gltf2_io_binary_data
|
from ...io.exp import gltf2_io_binary_data
|
||||||
|
from ...io.exp.gltf2_io_user_extensions import export_user_extensions
|
||||||
|
|
||||||
|
|
||||||
def gather_primitive_attributes(blender_primitive, export_settings):
|
def gather_primitive_attributes(blender_primitive, export_settings):
|
||||||
@ -151,6 +152,8 @@ def __gather_attribute(blender_primitive, attribute, export_settings):
|
|||||||
data['data'] += 0.5 # bias for rounding
|
data['data'] += 0.5 # bias for rounding
|
||||||
data['data'] = data['data'].astype(np.uint16)
|
data['data'] = data['data'].astype(np.uint16)
|
||||||
|
|
||||||
|
export_user_extensions('gather_attribute_change', export_settings, attribute, data, True)
|
||||||
|
|
||||||
return { attribute : gltf2_io.Accessor(
|
return { attribute : gltf2_io.Accessor(
|
||||||
buffer_view=gltf2_io_binary_data.BinaryData(data['data'].tobytes(), gltf2_io_constants.BufferViewTarget.ARRAY_BUFFER),
|
buffer_view=gltf2_io_binary_data.BinaryData(data['data'].tobytes(), gltf2_io_constants.BufferViewTarget.ARRAY_BUFFER),
|
||||||
byte_offset=None,
|
byte_offset=None,
|
||||||
@ -171,6 +174,9 @@ def __gather_attribute(blender_primitive, attribute, export_settings):
|
|||||||
return __gather_skins(blender_primitive, export_settings)
|
return __gather_skins(blender_primitive, export_settings)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
export_user_extensions('gather_attribute_change', export_settings, attribute, data, False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attribute: array_to_accessor(
|
attribute: array_to_accessor(
|
||||||
data['data'],
|
data['data'],
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Running Tests
|
# Running Tests
|
||||||
|
|
||||||
```
|
```
|
||||||
./util_test.py
|
./utils/paths_test.py
|
||||||
```
|
```
|
||||||
|
File diff suppressed because it is too large
Load Diff
520
node_wrangler/interface.py
Normal file
520
node_wrangler/interface.py
Normal file
@ -0,0 +1,520 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Panel, Menu
|
||||||
|
from bpy.props import StringProperty
|
||||||
|
from nodeitems_utils import node_categories_iter, NodeItemCustom
|
||||||
|
|
||||||
|
from . import operators
|
||||||
|
|
||||||
|
from .utils.constants import blend_types, geo_combine_operations, operations
|
||||||
|
from .utils.nodes import get_nodes_links, nw_check, NWBase
|
||||||
|
|
||||||
|
|
||||||
|
def drawlayout(context, layout, mode='non-panel'):
|
||||||
|
tree_type = context.space_data.tree_type
|
||||||
|
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.menu(NWMergeNodesMenu.bl_idname)
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.menu(NWSwitchNodeTypeMenu.bl_idname, text="Switch Node Type")
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
if tree_type == 'ShaderNodeTree':
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.operator(operators.NWAddTextureSetup.bl_idname, text="Add Texture Setup", icon='NODE_SEL')
|
||||||
|
col.operator(operators.NWAddPrincipledSetup.bl_idname, text="Add Principled Setup", icon='NODE_SEL')
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.operator(operators.NWDetachOutputs.bl_idname, icon='UNLINKED')
|
||||||
|
col.operator(operators.NWSwapLinks.bl_idname)
|
||||||
|
col.menu(NWAddReroutesMenu.bl_idname, text="Add Reroutes", icon='LAYER_USED')
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.menu(NWLinkActiveToSelectedMenu.bl_idname, text="Link Active To Selected", icon='LINKED')
|
||||||
|
if tree_type != 'GeometryNodeTree':
|
||||||
|
col.operator(operators.NWLinkToOutputNode.bl_idname, icon='DRIVER')
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
col = layout.column(align=True)
|
||||||
|
if mode == 'panel':
|
||||||
|
row = col.row(align=True)
|
||||||
|
row.operator(operators.NWClearLabel.bl_idname).option = True
|
||||||
|
row.operator(operators.NWModifyLabels.bl_idname)
|
||||||
|
else:
|
||||||
|
col.operator(operators.NWClearLabel.bl_idname).option = True
|
||||||
|
col.operator(operators.NWModifyLabels.bl_idname)
|
||||||
|
col.menu(NWBatchChangeNodesMenu.bl_idname, text="Batch Change")
|
||||||
|
col.separator()
|
||||||
|
col.menu(NWCopyToSelectedMenu.bl_idname, text="Copy to Selected")
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
col = layout.column(align=True)
|
||||||
|
if tree_type == 'CompositorNodeTree':
|
||||||
|
col.operator(operators.NWResetBG.bl_idname, icon='ZOOM_PREVIOUS')
|
||||||
|
if tree_type != 'GeometryNodeTree':
|
||||||
|
col.operator(operators.NWReloadImages.bl_idname, icon='FILE_REFRESH')
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.operator(operators.NWFrameSelected.bl_idname, icon='STICKY_UVS_LOC')
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.operator(operators.NWAlignNodes.bl_idname, icon='CENTER_ONLY')
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.operator(operators.NWDeleteUnused.bl_idname, icon='CANCEL')
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
|
||||||
|
class NodeWranglerPanel(Panel, NWBase):
|
||||||
|
bl_idname = "NODE_PT_nw_node_wrangler"
|
||||||
|
bl_space_type = 'NODE_EDITOR'
|
||||||
|
bl_label = "Node Wrangler"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Node Wrangler"
|
||||||
|
|
||||||
|
prepend: StringProperty(
|
||||||
|
name='prepend',
|
||||||
|
)
|
||||||
|
append: StringProperty()
|
||||||
|
remove: StringProperty()
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
self.layout.label(text="(Quick access: Shift+W)")
|
||||||
|
drawlayout(context, self.layout, mode='panel')
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# M E N U S
|
||||||
|
#
|
||||||
|
class NodeWranglerMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_node_wrangler_menu"
|
||||||
|
bl_label = "Node Wrangler"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
self.layout.operator_context = 'INVOKE_DEFAULT'
|
||||||
|
drawlayout(context, self.layout)
|
||||||
|
|
||||||
|
|
||||||
|
class NWMergeNodesMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_merge_nodes_menu"
|
||||||
|
bl_label = "Merge Selected Nodes"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
type = context.space_data.tree_type
|
||||||
|
layout = self.layout
|
||||||
|
if type == 'ShaderNodeTree':
|
||||||
|
layout.menu(NWMergeShadersMenu.bl_idname, text="Use Shaders")
|
||||||
|
if type == 'GeometryNodeTree':
|
||||||
|
layout.menu(NWMergeGeometryMenu.bl_idname, text="Use Geometry Nodes")
|
||||||
|
layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
|
||||||
|
else:
|
||||||
|
layout.menu(NWMergeMixMenu.bl_idname, text="Use Mix Nodes")
|
||||||
|
layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
|
||||||
|
props = layout.operator(operators.NWMergeNodes.bl_idname, text="Use Z-Combine Nodes")
|
||||||
|
props.mode = 'MIX'
|
||||||
|
props.merge_type = 'ZCOMBINE'
|
||||||
|
props = layout.operator(operators.NWMergeNodes.bl_idname, text="Use Alpha Over Nodes")
|
||||||
|
props.mode = 'MIX'
|
||||||
|
props.merge_type = 'ALPHAOVER'
|
||||||
|
|
||||||
|
|
||||||
|
class NWMergeGeometryMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_merge_geometry_menu"
|
||||||
|
bl_label = "Merge Selected Nodes using Geometry Nodes"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
# The boolean node + Join Geometry node
|
||||||
|
for type, name, description in geo_combine_operations:
|
||||||
|
props = layout.operator(operators.NWMergeNodes.bl_idname, text=name)
|
||||||
|
props.mode = type
|
||||||
|
props.merge_type = 'GEOMETRY'
|
||||||
|
|
||||||
|
|
||||||
|
class NWMergeShadersMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_merge_shaders_menu"
|
||||||
|
bl_label = "Merge Selected Nodes using Shaders"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
for type in ('MIX', 'ADD'):
|
||||||
|
props = layout.operator(operators.NWMergeNodes.bl_idname, text=type)
|
||||||
|
props.mode = type
|
||||||
|
props.merge_type = 'SHADER'
|
||||||
|
|
||||||
|
|
||||||
|
class NWMergeMixMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_merge_mix_menu"
|
||||||
|
bl_label = "Merge Selected Nodes using Mix"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
for type, name, description in blend_types:
|
||||||
|
props = layout.operator(operators.NWMergeNodes.bl_idname, text=name)
|
||||||
|
props.mode = type
|
||||||
|
props.merge_type = 'MIX'
|
||||||
|
|
||||||
|
|
||||||
|
class NWConnectionListOutputs(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_connection_list_out"
|
||||||
|
bl_label = "From:"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
nodes, links = get_nodes_links(context)
|
||||||
|
|
||||||
|
n1 = nodes[context.scene.NWLazySource]
|
||||||
|
for index, output in enumerate(n1.outputs):
|
||||||
|
# Only show sockets that are exposed.
|
||||||
|
if output.enabled:
|
||||||
|
layout.operator(
|
||||||
|
operators.NWCallInputsMenu.bl_idname,
|
||||||
|
text=output.name,
|
||||||
|
icon="RADIOBUT_OFF").from_socket = index
|
||||||
|
|
||||||
|
|
||||||
|
class NWConnectionListInputs(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_connection_list_in"
|
||||||
|
bl_label = "To:"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
nodes, links = get_nodes_links(context)
|
||||||
|
|
||||||
|
n2 = nodes[context.scene.NWLazyTarget]
|
||||||
|
|
||||||
|
for index, input in enumerate(n2.inputs):
|
||||||
|
# Only show sockets that are exposed.
|
||||||
|
# This prevents, for example, the scale value socket
|
||||||
|
# of the vector math node being added to the list when
|
||||||
|
# the mode is not 'SCALE'.
|
||||||
|
if input.enabled:
|
||||||
|
op = layout.operator(operators.NWMakeLink.bl_idname, text=input.name, icon="FORWARD")
|
||||||
|
op.from_socket = context.scene.NWSourceSocket
|
||||||
|
op.to_socket = index
|
||||||
|
|
||||||
|
|
||||||
|
class NWMergeMathMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_merge_math_menu"
|
||||||
|
bl_label = "Merge Selected Nodes using Math"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
for type, name, description in operations:
|
||||||
|
props = layout.operator(operators.NWMergeNodes.bl_idname, text=name)
|
||||||
|
props.mode = type
|
||||||
|
props.merge_type = 'MATH'
|
||||||
|
|
||||||
|
|
||||||
|
class NWBatchChangeNodesMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_batch_change_nodes_menu"
|
||||||
|
bl_label = "Batch Change Selected Nodes"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.menu(NWBatchChangeBlendTypeMenu.bl_idname)
|
||||||
|
layout.menu(NWBatchChangeOperationMenu.bl_idname)
|
||||||
|
|
||||||
|
|
||||||
|
class NWBatchChangeBlendTypeMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_batch_change_blend_type_menu"
|
||||||
|
bl_label = "Batch Change Blend Type"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
for type, name, description in blend_types:
|
||||||
|
props = layout.operator(operators.NWBatchChangeNodes.bl_idname, text=name)
|
||||||
|
props.blend_type = type
|
||||||
|
props.operation = 'CURRENT'
|
||||||
|
|
||||||
|
|
||||||
|
class NWBatchChangeOperationMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_batch_change_operation_menu"
|
||||||
|
bl_label = "Batch Change Math Operation"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
for type, name, description in operations:
|
||||||
|
props = layout.operator(operators.NWBatchChangeNodes.bl_idname, text=name)
|
||||||
|
props.blend_type = 'CURRENT'
|
||||||
|
props.operation = type
|
||||||
|
|
||||||
|
|
||||||
|
class NWCopyToSelectedMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_copy_node_properties_menu"
|
||||||
|
bl_label = "Copy to Selected"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator(operators.NWCopySettings.bl_idname, text="Settings from Active")
|
||||||
|
layout.menu(NWCopyLabelMenu.bl_idname)
|
||||||
|
|
||||||
|
|
||||||
|
class NWCopyLabelMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_copy_label_menu"
|
||||||
|
bl_label = "Copy Label"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator(operators.NWCopyLabel.bl_idname, text="from Active Node's Label").option = 'FROM_ACTIVE'
|
||||||
|
layout.operator(operators.NWCopyLabel.bl_idname, text="from Linked Node's Label").option = 'FROM_NODE'
|
||||||
|
layout.operator(operators.NWCopyLabel.bl_idname, text="from Linked Output's Name").option = 'FROM_SOCKET'
|
||||||
|
|
||||||
|
|
||||||
|
class NWAddReroutesMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_add_reroutes_menu"
|
||||||
|
bl_label = "Add Reroutes"
|
||||||
|
bl_description = "Add Reroute Nodes to Selected Nodes' Outputs"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator(operators.NWAddReroutes.bl_idname, text="to All Outputs").option = 'ALL'
|
||||||
|
layout.operator(operators.NWAddReroutes.bl_idname, text="to Loose Outputs").option = 'LOOSE'
|
||||||
|
layout.operator(operators.NWAddReroutes.bl_idname, text="to Linked Outputs").option = 'LINKED'
|
||||||
|
|
||||||
|
|
||||||
|
class NWLinkActiveToSelectedMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_link_active_to_selected_menu"
|
||||||
|
bl_label = "Link Active to Selected"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.menu(NWLinkStandardMenu.bl_idname)
|
||||||
|
layout.menu(NWLinkUseNodeNameMenu.bl_idname)
|
||||||
|
layout.menu(NWLinkUseOutputsNamesMenu.bl_idname)
|
||||||
|
|
||||||
|
|
||||||
|
class NWLinkStandardMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_link_standard_menu"
|
||||||
|
bl_label = "To All Selected"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
|
||||||
|
props.replace = False
|
||||||
|
props.use_node_name = False
|
||||||
|
props.use_outputs_names = False
|
||||||
|
props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Replace Links")
|
||||||
|
props.replace = True
|
||||||
|
props.use_node_name = False
|
||||||
|
props.use_outputs_names = False
|
||||||
|
|
||||||
|
|
||||||
|
class NWLinkUseNodeNameMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_link_use_node_name_menu"
|
||||||
|
bl_label = "Use Node Name/Label"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
|
||||||
|
props.replace = False
|
||||||
|
props.use_node_name = True
|
||||||
|
props.use_outputs_names = False
|
||||||
|
props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Replace Links")
|
||||||
|
props.replace = True
|
||||||
|
props.use_node_name = True
|
||||||
|
props.use_outputs_names = False
|
||||||
|
|
||||||
|
|
||||||
|
class NWLinkUseOutputsNamesMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_link_use_outputs_names_menu"
|
||||||
|
bl_label = "Use Outputs Names"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
|
||||||
|
props.replace = False
|
||||||
|
props.use_node_name = False
|
||||||
|
props.use_outputs_names = True
|
||||||
|
props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Replace Links")
|
||||||
|
props.replace = True
|
||||||
|
props.use_node_name = False
|
||||||
|
props.use_outputs_names = True
|
||||||
|
|
||||||
|
|
||||||
|
class NWAttributeMenu(bpy.types.Menu):
|
||||||
|
bl_idname = "NODE_MT_nw_node_attribute_menu"
|
||||||
|
bl_label = "Attributes"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
valid = False
|
||||||
|
if nw_check(context):
|
||||||
|
snode = context.space_data
|
||||||
|
valid = snode.tree_type == 'ShaderNodeTree'
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
l = self.layout
|
||||||
|
nodes, links = get_nodes_links(context)
|
||||||
|
mat = context.object.active_material
|
||||||
|
|
||||||
|
objs = []
|
||||||
|
for obj in bpy.data.objects:
|
||||||
|
for slot in obj.material_slots:
|
||||||
|
if slot.material == mat:
|
||||||
|
objs.append(obj)
|
||||||
|
attrs = []
|
||||||
|
for obj in objs:
|
||||||
|
if obj.data.attributes:
|
||||||
|
for attr in obj.data.attributes:
|
||||||
|
attrs.append(attr.name)
|
||||||
|
attrs = list(set(attrs)) # get a unique list
|
||||||
|
|
||||||
|
if attrs:
|
||||||
|
for attr in attrs:
|
||||||
|
l.operator(operators.NWAddAttrNode.bl_idname, text=attr).attr_name = attr
|
||||||
|
else:
|
||||||
|
l.label(text="No attributes on objects with this material")
|
||||||
|
|
||||||
|
|
||||||
|
class NWSwitchNodeTypeMenu(Menu, NWBase):
|
||||||
|
bl_idname = "NODE_MT_nw_switch_node_type_menu"
|
||||||
|
bl_label = "Switch Type to..."
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
categories = [c for c in node_categories_iter(context)
|
||||||
|
if c.name not in ['Group', 'Script']]
|
||||||
|
for cat in categories:
|
||||||
|
idname = f"NODE_MT_nw_switch_{cat.identifier}_submenu"
|
||||||
|
if hasattr(bpy.types, idname):
|
||||||
|
layout.menu(idname)
|
||||||
|
else:
|
||||||
|
layout.label(text="Unable to load altered node lists.")
|
||||||
|
layout.label(text="Please re-enable Node Wrangler.")
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def draw_switch_category_submenu(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
if self.category.name == 'Layout':
|
||||||
|
for node in self.category.items(context):
|
||||||
|
if node.nodetype != 'NodeFrame':
|
||||||
|
props = layout.operator(operators.NWSwitchNodeType.bl_idname, text=node.label)
|
||||||
|
props.to_type = node.nodetype
|
||||||
|
else:
|
||||||
|
for node in self.category.items(context):
|
||||||
|
if isinstance(node, NodeItemCustom):
|
||||||
|
node.draw(self, layout, context)
|
||||||
|
continue
|
||||||
|
props = layout.operator(operators.NWSwitchNodeType.bl_idname, text=node.label)
|
||||||
|
props.to_type = node.nodetype
|
||||||
|
|
||||||
|
#
|
||||||
|
# APPENDAGES TO EXISTING UI
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
def select_parent_children_buttons(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator(operators.NWSelectParentChildren.bl_idname,
|
||||||
|
text="Select frame's members (children)").option = 'CHILD'
|
||||||
|
layout.operator(operators.NWSelectParentChildren.bl_idname, text="Select parent frame").option = 'PARENT'
|
||||||
|
|
||||||
|
|
||||||
|
def attr_nodes_menu_func(self, context):
|
||||||
|
col = self.layout.column(align=True)
|
||||||
|
col.menu("NODE_MT_nw_node_attribute_menu")
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
|
||||||
|
def multipleimages_menu_func(self, context):
|
||||||
|
col = self.layout.column(align=True)
|
||||||
|
col.operator(operators.NWAddMultipleImages.bl_idname, text="Multiple Images")
|
||||||
|
col.operator(operators.NWAddSequence.bl_idname, text="Image Sequence")
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
|
||||||
|
def bgreset_menu_func(self, context):
|
||||||
|
self.layout.operator(operators.NWResetBG.bl_idname)
|
||||||
|
|
||||||
|
|
||||||
|
def save_viewer_menu_func(self, context):
|
||||||
|
if nw_check(context):
|
||||||
|
if context.space_data.tree_type == 'CompositorNodeTree':
|
||||||
|
if context.scene.node_tree.nodes.active:
|
||||||
|
if context.scene.node_tree.nodes.active.type == "VIEWER":
|
||||||
|
self.layout.operator(operators.NWSaveViewer.bl_idname, icon='FILE_IMAGE')
|
||||||
|
|
||||||
|
|
||||||
|
def reset_nodes_button(self, context):
|
||||||
|
node_active = context.active_node
|
||||||
|
node_selected = context.selected_nodes
|
||||||
|
node_ignore = ["FRAME", "REROUTE", "GROUP"]
|
||||||
|
|
||||||
|
# Check if active node is in the selection and respective type
|
||||||
|
if (len(node_selected) == 1) and node_active and node_active.select and node_active.type not in node_ignore:
|
||||||
|
row = self.layout.row()
|
||||||
|
row.operator(operators.NWResetNodes.bl_idname, text="Reset Node", icon="FILE_REFRESH")
|
||||||
|
self.layout.separator()
|
||||||
|
|
||||||
|
elif (len(node_selected) == 1) and node_active and node_active.select and node_active.type == "FRAME":
|
||||||
|
row = self.layout.row()
|
||||||
|
row.operator(operators.NWResetNodes.bl_idname, text="Reset Nodes in Frame", icon="FILE_REFRESH")
|
||||||
|
self.layout.separator()
|
||||||
|
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
NodeWranglerPanel,
|
||||||
|
NodeWranglerMenu,
|
||||||
|
NWMergeNodesMenu,
|
||||||
|
NWMergeGeometryMenu,
|
||||||
|
NWMergeShadersMenu,
|
||||||
|
NWMergeMixMenu,
|
||||||
|
NWConnectionListOutputs,
|
||||||
|
NWConnectionListInputs,
|
||||||
|
NWMergeMathMenu,
|
||||||
|
NWBatchChangeNodesMenu,
|
||||||
|
NWBatchChangeBlendTypeMenu,
|
||||||
|
NWBatchChangeOperationMenu,
|
||||||
|
NWCopyToSelectedMenu,
|
||||||
|
NWCopyLabelMenu,
|
||||||
|
NWAddReroutesMenu,
|
||||||
|
NWLinkActiveToSelectedMenu,
|
||||||
|
NWLinkStandardMenu,
|
||||||
|
NWLinkUseNodeNameMenu,
|
||||||
|
NWLinkUseOutputsNamesMenu,
|
||||||
|
NWAttributeMenu,
|
||||||
|
NWSwitchNodeTypeMenu,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
from bpy.utils import register_class
|
||||||
|
for cls in classes:
|
||||||
|
register_class(cls)
|
||||||
|
|
||||||
|
# menu items
|
||||||
|
bpy.types.NODE_MT_select.append(select_parent_children_buttons)
|
||||||
|
bpy.types.NODE_MT_category_SH_NEW_INPUT.prepend(attr_nodes_menu_func)
|
||||||
|
bpy.types.NODE_PT_backdrop.append(bgreset_menu_func)
|
||||||
|
bpy.types.NODE_PT_active_node_generic.append(save_viewer_menu_func)
|
||||||
|
bpy.types.NODE_MT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func)
|
||||||
|
bpy.types.NODE_MT_category_CMP_INPUT.prepend(multipleimages_menu_func)
|
||||||
|
bpy.types.NODE_PT_active_node_generic.prepend(reset_nodes_button)
|
||||||
|
bpy.types.NODE_MT_node.prepend(reset_nodes_button)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
# menu items
|
||||||
|
bpy.types.NODE_MT_select.remove(select_parent_children_buttons)
|
||||||
|
bpy.types.NODE_MT_category_SH_NEW_INPUT.remove(attr_nodes_menu_func)
|
||||||
|
bpy.types.NODE_PT_backdrop.remove(bgreset_menu_func)
|
||||||
|
bpy.types.NODE_PT_active_node_generic.remove(save_viewer_menu_func)
|
||||||
|
bpy.types.NODE_MT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func)
|
||||||
|
bpy.types.NODE_MT_category_CMP_INPUT.remove(multipleimages_menu_func)
|
||||||
|
bpy.types.NODE_PT_active_node_generic.remove(reset_nodes_button)
|
||||||
|
bpy.types.NODE_MT_node.remove(reset_nodes_button)
|
||||||
|
|
||||||
|
from bpy.utils import unregister_class
|
||||||
|
for cls in classes:
|
||||||
|
unregister_class(cls)
|
3015
node_wrangler/operators.py
Normal file
3015
node_wrangler/operators.py
Normal file
File diff suppressed because it is too large
Load Diff
428
node_wrangler/preferences.py
Normal file
428
node_wrangler/preferences.py
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.props import EnumProperty, BoolProperty, StringProperty
|
||||||
|
from nodeitems_utils import node_categories_iter
|
||||||
|
|
||||||
|
from . import operators
|
||||||
|
from . import interface
|
||||||
|
|
||||||
|
from .utils.constants import nice_hotkey_name
|
||||||
|
|
||||||
|
|
||||||
|
# Principled prefs
|
||||||
|
class NWPrincipledPreferences(bpy.types.PropertyGroup):
|
||||||
|
base_color: StringProperty(
|
||||||
|
name='Base Color',
|
||||||
|
default='diffuse diff albedo base col color basecolor',
|
||||||
|
description='Naming Components for Base Color maps')
|
||||||
|
sss_color: StringProperty(
|
||||||
|
name='Subsurface Color',
|
||||||
|
default='sss subsurface',
|
||||||
|
description='Naming Components for Subsurface Color maps')
|
||||||
|
metallic: StringProperty(
|
||||||
|
name='Metallic',
|
||||||
|
default='metallic metalness metal mtl',
|
||||||
|
description='Naming Components for metallness maps')
|
||||||
|
specular: StringProperty(
|
||||||
|
name='Specular',
|
||||||
|
default='specularity specular spec spc',
|
||||||
|
description='Naming Components for Specular maps')
|
||||||
|
normal: StringProperty(
|
||||||
|
name='Normal',
|
||||||
|
default='normal nor nrm nrml norm',
|
||||||
|
description='Naming Components for Normal maps')
|
||||||
|
bump: StringProperty(
|
||||||
|
name='Bump',
|
||||||
|
default='bump bmp',
|
||||||
|
description='Naming Components for bump maps')
|
||||||
|
rough: StringProperty(
|
||||||
|
name='Roughness',
|
||||||
|
default='roughness rough rgh',
|
||||||
|
description='Naming Components for roughness maps')
|
||||||
|
gloss: StringProperty(
|
||||||
|
name='Gloss',
|
||||||
|
default='gloss glossy glossiness',
|
||||||
|
description='Naming Components for glossy maps')
|
||||||
|
displacement: StringProperty(
|
||||||
|
name='Displacement',
|
||||||
|
default='displacement displace disp dsp height heightmap',
|
||||||
|
description='Naming Components for displacement maps')
|
||||||
|
transmission: StringProperty(
|
||||||
|
name='Transmission',
|
||||||
|
default='transmission transparency',
|
||||||
|
description='Naming Components for transmission maps')
|
||||||
|
emission: StringProperty(
|
||||||
|
name='Emission',
|
||||||
|
default='emission emissive emit',
|
||||||
|
description='Naming Components for emission maps')
|
||||||
|
alpha: StringProperty(
|
||||||
|
name='Alpha',
|
||||||
|
default='alpha opacity',
|
||||||
|
description='Naming Components for alpha maps')
|
||||||
|
ambient_occlusion: StringProperty(
|
||||||
|
name='Ambient Occlusion',
|
||||||
|
default='ao ambient occlusion',
|
||||||
|
description='Naming Components for AO maps')
|
||||||
|
|
||||||
|
|
||||||
|
# Addon prefs
|
||||||
|
class NWNodeWrangler(bpy.types.AddonPreferences):
|
||||||
|
bl_idname = __package__
|
||||||
|
|
||||||
|
merge_hide: EnumProperty(
|
||||||
|
name="Hide Mix nodes",
|
||||||
|
items=(
|
||||||
|
("ALWAYS", "Always", "Always collapse the new merge nodes"),
|
||||||
|
("NON_SHADER", "Non-Shader", "Collapse in all cases except for shaders"),
|
||||||
|
("NEVER", "Never", "Never collapse the new merge nodes")
|
||||||
|
),
|
||||||
|
default='NON_SHADER',
|
||||||
|
description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify whether to collapse them or show the full node with options expanded")
|
||||||
|
merge_position: EnumProperty(
|
||||||
|
name="Mix Node Position",
|
||||||
|
items=(
|
||||||
|
("CENTER", "Center", "Place the Mix node between the two nodes"),
|
||||||
|
("BOTTOM", "Bottom", "Place the Mix node at the same height as the lowest node")
|
||||||
|
),
|
||||||
|
default='CENTER',
|
||||||
|
description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes")
|
||||||
|
|
||||||
|
show_hotkey_list: BoolProperty(
|
||||||
|
name="Show Hotkey List",
|
||||||
|
default=False,
|
||||||
|
description="Expand this box into a list of all the hotkeys for functions in this addon"
|
||||||
|
)
|
||||||
|
hotkey_list_filter: StringProperty(
|
||||||
|
name=" Filter by Name",
|
||||||
|
default="",
|
||||||
|
description="Show only hotkeys that have this text in their name",
|
||||||
|
options={'TEXTEDIT_UPDATE'}
|
||||||
|
)
|
||||||
|
show_principled_lists: BoolProperty(
|
||||||
|
name="Show Principled naming tags",
|
||||||
|
default=False,
|
||||||
|
description="Expand this box into a list of all naming tags for principled texture setup"
|
||||||
|
)
|
||||||
|
principled_tags: bpy.props.PointerProperty(type=NWPrincipledPreferences)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
col = layout.column()
|
||||||
|
col.prop(self, "merge_position")
|
||||||
|
col.prop(self, "merge_hide")
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
col = box.column(align=True)
|
||||||
|
col.prop(
|
||||||
|
self,
|
||||||
|
"show_principled_lists",
|
||||||
|
text='Edit tags for auto texture detection in Principled BSDF setup',
|
||||||
|
toggle=True)
|
||||||
|
if self.show_principled_lists:
|
||||||
|
tags = self.principled_tags
|
||||||
|
|
||||||
|
col.prop(tags, "base_color")
|
||||||
|
col.prop(tags, "sss_color")
|
||||||
|
col.prop(tags, "metallic")
|
||||||
|
col.prop(tags, "specular")
|
||||||
|
col.prop(tags, "rough")
|
||||||
|
col.prop(tags, "gloss")
|
||||||
|
col.prop(tags, "normal")
|
||||||
|
col.prop(tags, "bump")
|
||||||
|
col.prop(tags, "displacement")
|
||||||
|
col.prop(tags, "transmission")
|
||||||
|
col.prop(tags, "emission")
|
||||||
|
col.prop(tags, "alpha")
|
||||||
|
col.prop(tags, "ambient_occlusion")
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
col = box.column(align=True)
|
||||||
|
hotkey_button_name = "Show Hotkey List"
|
||||||
|
if self.show_hotkey_list:
|
||||||
|
hotkey_button_name = "Hide Hotkey List"
|
||||||
|
col.prop(self, "show_hotkey_list", text=hotkey_button_name, toggle=True)
|
||||||
|
if self.show_hotkey_list:
|
||||||
|
col.prop(self, "hotkey_list_filter", icon="VIEWZOOM")
|
||||||
|
col.separator()
|
||||||
|
for hotkey in kmi_defs:
|
||||||
|
if hotkey[7]:
|
||||||
|
hotkey_name = hotkey[7]
|
||||||
|
|
||||||
|
if self.hotkey_list_filter.lower() in hotkey_name.lower():
|
||||||
|
row = col.row(align=True)
|
||||||
|
row.label(text=hotkey_name)
|
||||||
|
keystr = nice_hotkey_name(hotkey[1])
|
||||||
|
if hotkey[4]:
|
||||||
|
keystr = "Shift " + keystr
|
||||||
|
if hotkey[5]:
|
||||||
|
keystr = "Alt " + keystr
|
||||||
|
if hotkey[3]:
|
||||||
|
keystr = "Ctrl " + keystr
|
||||||
|
row.label(text=keystr)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# REGISTER/UNREGISTER CLASSES AND KEYMAP ITEMS
|
||||||
|
#
|
||||||
|
switch_category_menus = []
|
||||||
|
addon_keymaps = []
|
||||||
|
# kmi_defs entry: (identifier, key, action, CTRL, SHIFT, ALT, props, nice name)
|
||||||
|
# props entry: (property name, property value)
|
||||||
|
kmi_defs = (
|
||||||
|
# MERGE NODES
|
||||||
|
# NWMergeNodes with Ctrl (AUTO).
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_0', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'ZERO', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'MIX'), ('merge_type', 'AUTO'),), "Merge Nodes (Automatic)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_PLUS', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'EQUAL', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'ADD'), ('merge_type', 'AUTO'),), "Merge Nodes (Add)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'EIGHT', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'MULTIPLY'), ('merge_type', 'AUTO'),), "Merge Nodes (Multiply)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_MINUS', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'MINUS', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'SUBTRACT'), ('merge_type', 'AUTO'),), "Merge Nodes (Subtract)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_SLASH', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'SLASH', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'DIVIDE'), ('merge_type', 'AUTO'),), "Merge Nodes (Divide)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'COMMA', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Less than)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'PERIOD', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Greater than)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_PERIOD', 'PRESS', True, False, False,
|
||||||
|
(('mode', 'MIX'), ('merge_type', 'ZCOMBINE'),), "Merge Nodes (Z-Combine)"),
|
||||||
|
# NWMergeNodes with Ctrl Alt (MIX or ALPHAOVER)
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_0', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'ZERO', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'MIX'), ('merge_type', 'ALPHAOVER'),), "Merge Nodes (Alpha Over)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_PLUS', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'EQUAL', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'ADD'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Add)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'EIGHT', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'MULTIPLY'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Multiply)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_MINUS', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'MINUS', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'SUBTRACT'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Subtract)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_SLASH', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'SLASH', 'PRESS', True, False, True,
|
||||||
|
(('mode', 'DIVIDE'), ('merge_type', 'MIX'),), "Merge Nodes (Color, Divide)"),
|
||||||
|
# NWMergeNodes with Ctrl Shift (MATH)
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_PLUS', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'EQUAL', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'ADD'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Add)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'EIGHT', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'MULTIPLY'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Multiply)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_MINUS', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'MINUS', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'SUBTRACT'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Subtract)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'NUMPAD_SLASH', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'SLASH', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'DIVIDE'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Divide)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'COMMA', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'LESS_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Less than)"),
|
||||||
|
(operators.NWMergeNodes.bl_idname, 'PERIOD', 'PRESS', True, True, False,
|
||||||
|
(('mode', 'GREATER_THAN'), ('merge_type', 'MATH'),), "Merge Nodes (Math, Greater than)"),
|
||||||
|
# BATCH CHANGE NODES
|
||||||
|
# NWBatchChangeNodes with Alt
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'NUMPAD_0', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'ZERO', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'MIX'), ('operation', 'CURRENT'),), "Batch change blend type (Mix)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'NUMPAD_PLUS', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'EQUAL', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'ADD'), ('operation', 'ADD'),), "Batch change blend type (Add)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'NUMPAD_ASTERIX', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'EIGHT', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'MULTIPLY'), ('operation', 'MULTIPLY'),), "Batch change blend type (Multiply)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'NUMPAD_MINUS', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'MINUS', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'SUBTRACT'), ('operation', 'SUBTRACT'),), "Batch change blend type (Subtract)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'NUMPAD_SLASH', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'SLASH', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'DIVIDE'), ('operation', 'DIVIDE'),), "Batch change blend type (Divide)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'COMMA', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'CURRENT'), ('operation', 'LESS_THAN'),), "Batch change blend type (Current)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'PERIOD', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'CURRENT'), ('operation', 'GREATER_THAN'),), "Batch change blend type (Current)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'DOWN_ARROW', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'NEXT'), ('operation', 'NEXT'),), "Batch change blend type (Next)"),
|
||||||
|
(operators.NWBatchChangeNodes.bl_idname, 'UP_ARROW', 'PRESS', False, False, True,
|
||||||
|
(('blend_type', 'PREV'), ('operation', 'PREV'),), "Batch change blend type (Previous)"),
|
||||||
|
# LINK ACTIVE TO SELECTED
|
||||||
|
# Don't use names, don't replace links (K)
|
||||||
|
(operators.NWLinkActiveToSelected.bl_idname, 'K', 'PRESS', False, False, False,
|
||||||
|
(('replace', False), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Don't replace links)"),
|
||||||
|
# Don't use names, replace links (Shift K)
|
||||||
|
(operators.NWLinkActiveToSelected.bl_idname, 'K', 'PRESS', False, True, False,
|
||||||
|
(('replace', True), ('use_node_name', False), ('use_outputs_names', False),), "Link active to selected (Replace links)"),
|
||||||
|
# Use node name, don't replace links (')
|
||||||
|
(operators.NWLinkActiveToSelected.bl_idname, 'QUOTE', 'PRESS', False, False, False,
|
||||||
|
(('replace', False), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Don't replace links, node names)"),
|
||||||
|
# Use node name, replace links (Shift ')
|
||||||
|
(operators.NWLinkActiveToSelected.bl_idname, 'QUOTE', 'PRESS', False, True, False,
|
||||||
|
(('replace', True), ('use_node_name', True), ('use_outputs_names', False),), "Link active to selected (Replace links, node names)"),
|
||||||
|
# Don't use names, don't replace links (;)
|
||||||
|
(operators.NWLinkActiveToSelected.bl_idname, 'SEMI_COLON', 'PRESS', False, False, False,
|
||||||
|
(('replace', False), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Don't replace links, output names)"),
|
||||||
|
# Don't use names, replace links (')
|
||||||
|
(operators.NWLinkActiveToSelected.bl_idname, 'SEMI_COLON', 'PRESS', False, True, False,
|
||||||
|
(('replace', True), ('use_node_name', False), ('use_outputs_names', True),), "Link active to selected (Replace links, output names)"),
|
||||||
|
# CHANGE MIX FACTOR
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', False,
|
||||||
|
False, True, (('option', -0.1),), "Reduce Mix Factor by 0.1"),
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', False,
|
||||||
|
False, True, (('option', 0.1),), "Increase Mix Factor by 0.1"),
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS', False,
|
||||||
|
True, True, (('option', -0.01),), "Reduce Mix Factor by 0.01"),
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS', False,
|
||||||
|
True, True, (('option', 0.01),), "Increase Mix Factor by 0.01"),
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'LEFT_ARROW', 'PRESS',
|
||||||
|
True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'RIGHT_ARROW', 'PRESS',
|
||||||
|
True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'NUMPAD_0', 'PRESS',
|
||||||
|
True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'ZERO', 'PRESS', True, True, True, (('option', 0.0),), "Set Mix Factor to 0.0"),
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'NUMPAD_1', 'PRESS', True, True, True, (('option', 1.0),), "Mix Factor to 1.0"),
|
||||||
|
(operators.NWChangeMixFactor.bl_idname, 'ONE', 'PRESS', True, True, True, (('option', 1.0),), "Set Mix Factor to 1.0"),
|
||||||
|
# CLEAR LABEL (Alt L)
|
||||||
|
(operators.NWClearLabel.bl_idname, 'L', 'PRESS', False, False, True, (('option', False),), "Clear node labels"),
|
||||||
|
# MODIFY LABEL (Alt Shift L)
|
||||||
|
(operators.NWModifyLabels.bl_idname, 'L', 'PRESS', False, True, True, None, "Modify node labels"),
|
||||||
|
# Copy Label from active to selected
|
||||||
|
(operators.NWCopyLabel.bl_idname, 'V', 'PRESS', False, True, False,
|
||||||
|
(('option', 'FROM_ACTIVE'),), "Copy label from active to selected"),
|
||||||
|
# DETACH OUTPUTS (Alt Shift D)
|
||||||
|
(operators.NWDetachOutputs.bl_idname, 'D', 'PRESS', False, True, True, None, "Detach outputs"),
|
||||||
|
# LINK TO OUTPUT NODE (O)
|
||||||
|
(operators.NWLinkToOutputNode.bl_idname, 'O', 'PRESS', False, False, False, None, "Link to output node"),
|
||||||
|
# SELECT PARENT/CHILDREN
|
||||||
|
# Select Children
|
||||||
|
(operators.NWSelectParentChildren.bl_idname, 'RIGHT_BRACKET', 'PRESS',
|
||||||
|
False, False, False, (('option', 'CHILD'),), "Select children"),
|
||||||
|
# Select Parent
|
||||||
|
(operators.NWSelectParentChildren.bl_idname, 'LEFT_BRACKET', 'PRESS',
|
||||||
|
False, False, False, (('option', 'PARENT'),), "Select Parent"),
|
||||||
|
# Add Texture Setup
|
||||||
|
(operators.NWAddTextureSetup.bl_idname, 'T', 'PRESS', True, False, False, None, "Add texture setup"),
|
||||||
|
# Add Principled BSDF Texture Setup
|
||||||
|
(operators.NWAddPrincipledSetup.bl_idname, 'T', 'PRESS', True, True, False, None, "Add Principled texture setup"),
|
||||||
|
# Reset backdrop
|
||||||
|
(operators.NWResetBG.bl_idname, 'Z', 'PRESS', False, False, False, None, "Reset backdrop image zoom"),
|
||||||
|
# Delete unused
|
||||||
|
(operators.NWDeleteUnused.bl_idname, 'X', 'PRESS', False, False, True, None, "Delete unused nodes"),
|
||||||
|
# Frame Selected
|
||||||
|
(operators.NWFrameSelected.bl_idname, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"),
|
||||||
|
# Swap Links
|
||||||
|
(operators.NWSwapLinks.bl_idname, 'S', 'PRESS', False, False, True, None, "Swap Links"),
|
||||||
|
# Preview Node
|
||||||
|
(operators.NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', True, True,
|
||||||
|
False, (('run_in_geometry_nodes', False),), "Preview node output"),
|
||||||
|
(operators.NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', False, True,
|
||||||
|
True, (('run_in_geometry_nodes', True),), "Preview node output"),
|
||||||
|
# Reload Images
|
||||||
|
(operators.NWReloadImages.bl_idname, 'R', 'PRESS', False, False, True, None, "Reload images"),
|
||||||
|
# Lazy Mix
|
||||||
|
(operators.NWLazyMix.bl_idname, 'RIGHTMOUSE', 'PRESS', True, True, False, None, "Lazy Mix"),
|
||||||
|
# Lazy Connect
|
||||||
|
(operators.NWLazyConnect.bl_idname, 'RIGHTMOUSE', 'PRESS', False, False, True, (('with_menu', False),), "Lazy Connect"),
|
||||||
|
# Lazy Connect with Menu
|
||||||
|
(operators.NWLazyConnect.bl_idname, 'RIGHTMOUSE', 'PRESS', False,
|
||||||
|
True, True, (('with_menu', True),), "Lazy Connect with Socket Menu"),
|
||||||
|
# Viewer Tile Center
|
||||||
|
(operators.NWViewerFocus.bl_idname, 'LEFTMOUSE', 'DOUBLE_CLICK', False, False, False, None, "Set Viewers Tile Center"),
|
||||||
|
# Align Nodes
|
||||||
|
(operators.NWAlignNodes.bl_idname, 'EQUAL', 'PRESS', False, True,
|
||||||
|
False, None, "Align selected nodes neatly in a row/column"),
|
||||||
|
# Reset Nodes (Back Space)
|
||||||
|
(operators.NWResetNodes.bl_idname, 'BACK_SPACE', 'PRESS', False, False,
|
||||||
|
False, None, "Revert node back to default state, but keep connections"),
|
||||||
|
# MENUS
|
||||||
|
('wm.call_menu', 'W', 'PRESS', False, True, False, (('name', interface.NodeWranglerMenu.bl_idname),), "Node Wrangler menu"),
|
||||||
|
('wm.call_menu', 'SLASH', 'PRESS', False, False, False,
|
||||||
|
(('name', interface.NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
|
||||||
|
('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False,
|
||||||
|
(('name', interface.NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"),
|
||||||
|
('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False,
|
||||||
|
(('name', interface.NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"),
|
||||||
|
('wm.call_menu', 'C', 'PRESS', False, True, False,
|
||||||
|
(('name', interface.NWCopyToSelectedMenu.bl_idname),), "Copy to selected (menu)"),
|
||||||
|
('wm.call_menu', 'S', 'PRESS', False, True, False,
|
||||||
|
(('name', interface.NWSwitchNodeTypeMenu.bl_idname),), "Switch node type menu"),
|
||||||
|
)
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
NWPrincipledPreferences, NWNodeWrangler
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
from bpy.utils import register_class
|
||||||
|
for cls in classes:
|
||||||
|
register_class(cls)
|
||||||
|
|
||||||
|
# keymaps
|
||||||
|
addon_keymaps.clear()
|
||||||
|
kc = bpy.context.window_manager.keyconfigs.addon
|
||||||
|
if kc:
|
||||||
|
km = kc.keymaps.new(name='Node Editor', space_type="NODE_EDITOR")
|
||||||
|
for (identifier, key, action, CTRL, SHIFT, ALT, props, nicename) in kmi_defs:
|
||||||
|
kmi = km.keymap_items.new(identifier, key, action, ctrl=CTRL, shift=SHIFT, alt=ALT)
|
||||||
|
if props:
|
||||||
|
for prop, value in props:
|
||||||
|
setattr(kmi.properties, prop, value)
|
||||||
|
addon_keymaps.append((km, kmi))
|
||||||
|
|
||||||
|
# switch submenus
|
||||||
|
switch_category_menus.clear()
|
||||||
|
for cat in node_categories_iter(None):
|
||||||
|
if cat.name not in ['Group', 'Script']:
|
||||||
|
idname = f"NODE_MT_nw_switch_{cat.identifier}_submenu"
|
||||||
|
switch_category_type = type(idname, (bpy.types.Menu,), {
|
||||||
|
"bl_space_type": 'NODE_EDITOR',
|
||||||
|
"bl_label": cat.name,
|
||||||
|
"category": cat,
|
||||||
|
"poll": cat.poll,
|
||||||
|
"draw": interface.draw_switch_category_submenu,
|
||||||
|
})
|
||||||
|
|
||||||
|
switch_category_menus.append(switch_category_type)
|
||||||
|
|
||||||
|
bpy.utils.register_class(switch_category_type)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for cat_types in switch_category_menus:
|
||||||
|
bpy.utils.unregister_class(cat_types)
|
||||||
|
switch_category_menus.clear()
|
||||||
|
|
||||||
|
# keymaps
|
||||||
|
for km, kmi in addon_keymaps:
|
||||||
|
km.keymap_items.remove(kmi)
|
||||||
|
addon_keymaps.clear()
|
||||||
|
|
||||||
|
from bpy.utils import unregister_class
|
||||||
|
for cls in classes:
|
||||||
|
unregister_class(cls)
|
218
node_wrangler/utils/constants.py
Normal file
218
node_wrangler/utils/constants.py
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
from nodeitems_utils import node_categories_iter
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
# rl_outputs:
|
||||||
|
# list of outputs of Input Render Layer
|
||||||
|
# with attributes determining if pass is used,
|
||||||
|
# and MultiLayer EXR outputs names and corresponding render engines
|
||||||
|
#
|
||||||
|
# rl_outputs entry = (render_pass, rl_output_name, exr_output_name, in_eevee, in_cycles)
|
||||||
|
RL_entry = namedtuple('RL_Entry', ['render_pass', 'output_name', 'exr_output_name', 'in_eevee', 'in_cycles'])
|
||||||
|
rl_outputs = (
|
||||||
|
RL_entry('use_pass_ambient_occlusion', 'AO', 'AO', True, True),
|
||||||
|
RL_entry('use_pass_combined', 'Image', 'Combined', True, True),
|
||||||
|
RL_entry('use_pass_diffuse_color', 'Diffuse Color', 'DiffCol', False, True),
|
||||||
|
RL_entry('use_pass_diffuse_direct', 'Diffuse Direct', 'DiffDir', False, True),
|
||||||
|
RL_entry('use_pass_diffuse_indirect', 'Diffuse Indirect', 'DiffInd', False, True),
|
||||||
|
RL_entry('use_pass_emit', 'Emit', 'Emit', False, True),
|
||||||
|
RL_entry('use_pass_environment', 'Environment', 'Env', False, False),
|
||||||
|
RL_entry('use_pass_glossy_color', 'Glossy Color', 'GlossCol', False, True),
|
||||||
|
RL_entry('use_pass_glossy_direct', 'Glossy Direct', 'GlossDir', False, True),
|
||||||
|
RL_entry('use_pass_glossy_indirect', 'Glossy Indirect', 'GlossInd', False, True),
|
||||||
|
RL_entry('use_pass_indirect', 'Indirect', 'Indirect', False, False),
|
||||||
|
RL_entry('use_pass_material_index', 'IndexMA', 'IndexMA', False, True),
|
||||||
|
RL_entry('use_pass_mist', 'Mist', 'Mist', True, True),
|
||||||
|
RL_entry('use_pass_normal', 'Normal', 'Normal', True, True),
|
||||||
|
RL_entry('use_pass_object_index', 'IndexOB', 'IndexOB', False, True),
|
||||||
|
RL_entry('use_pass_shadow', 'Shadow', 'Shadow', False, True),
|
||||||
|
RL_entry('use_pass_subsurface_color', 'Subsurface Color', 'SubsurfaceCol', True, True),
|
||||||
|
RL_entry('use_pass_subsurface_direct', 'Subsurface Direct', 'SubsurfaceDir', True, True),
|
||||||
|
RL_entry('use_pass_subsurface_indirect', 'Subsurface Indirect', 'SubsurfaceInd', False, True),
|
||||||
|
RL_entry('use_pass_transmission_color', 'Transmission Color', 'TransCol', False, True),
|
||||||
|
RL_entry('use_pass_transmission_direct', 'Transmission Direct', 'TransDir', False, True),
|
||||||
|
RL_entry('use_pass_transmission_indirect', 'Transmission Indirect', 'TransInd', False, True),
|
||||||
|
RL_entry('use_pass_uv', 'UV', 'UV', True, True),
|
||||||
|
RL_entry('use_pass_vector', 'Speed', 'Vector', False, True),
|
||||||
|
RL_entry('use_pass_z', 'Z', 'Depth', True, True),
|
||||||
|
)
|
||||||
|
|
||||||
|
# list of blend types of "Mix" nodes in a form that can be used as 'items' for EnumProperty.
|
||||||
|
# used list, not tuple for easy merging with other lists.
|
||||||
|
blend_types = [
|
||||||
|
('MIX', 'Mix', 'Mix Mode'),
|
||||||
|
('ADD', 'Add', 'Add Mode'),
|
||||||
|
('MULTIPLY', 'Multiply', 'Multiply Mode'),
|
||||||
|
('SUBTRACT', 'Subtract', 'Subtract Mode'),
|
||||||
|
('SCREEN', 'Screen', 'Screen Mode'),
|
||||||
|
('DIVIDE', 'Divide', 'Divide Mode'),
|
||||||
|
('DIFFERENCE', 'Difference', 'Difference Mode'),
|
||||||
|
('DARKEN', 'Darken', 'Darken Mode'),
|
||||||
|
('LIGHTEN', 'Lighten', 'Lighten Mode'),
|
||||||
|
('OVERLAY', 'Overlay', 'Overlay Mode'),
|
||||||
|
('DODGE', 'Dodge', 'Dodge Mode'),
|
||||||
|
('BURN', 'Burn', 'Burn Mode'),
|
||||||
|
('HUE', 'Hue', 'Hue Mode'),
|
||||||
|
('SATURATION', 'Saturation', 'Saturation Mode'),
|
||||||
|
('VALUE', 'Value', 'Value Mode'),
|
||||||
|
('COLOR', 'Color', 'Color Mode'),
|
||||||
|
('SOFT_LIGHT', 'Soft Light', 'Soft Light Mode'),
|
||||||
|
('LINEAR_LIGHT', 'Linear Light', 'Linear Light Mode'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# list of operations of "Math" nodes in a form that can be used as 'items' for EnumProperty.
|
||||||
|
# used list, not tuple for easy merging with other lists.
|
||||||
|
operations = [
|
||||||
|
('ADD', 'Add', 'Add Mode'),
|
||||||
|
('SUBTRACT', 'Subtract', 'Subtract Mode'),
|
||||||
|
('MULTIPLY', 'Multiply', 'Multiply Mode'),
|
||||||
|
('DIVIDE', 'Divide', 'Divide Mode'),
|
||||||
|
('MULTIPLY_ADD', 'Multiply Add', 'Multiply Add Mode'),
|
||||||
|
('SINE', 'Sine', 'Sine Mode'),
|
||||||
|
('COSINE', 'Cosine', 'Cosine Mode'),
|
||||||
|
('TANGENT', 'Tangent', 'Tangent Mode'),
|
||||||
|
('ARCSINE', 'Arcsine', 'Arcsine Mode'),
|
||||||
|
('ARCCOSINE', 'Arccosine', 'Arccosine Mode'),
|
||||||
|
('ARCTANGENT', 'Arctangent', 'Arctangent Mode'),
|
||||||
|
('ARCTAN2', 'Arctan2', 'Arctan2 Mode'),
|
||||||
|
('SINH', 'Hyperbolic Sine', 'Hyperbolic Sine Mode'),
|
||||||
|
('COSH', 'Hyperbolic Cosine', 'Hyperbolic Cosine Mode'),
|
||||||
|
('TANH', 'Hyperbolic Tangent', 'Hyperbolic Tangent Mode'),
|
||||||
|
('POWER', 'Power', 'Power Mode'),
|
||||||
|
('LOGARITHM', 'Logarithm', 'Logarithm Mode'),
|
||||||
|
('SQRT', 'Square Root', 'Square Root Mode'),
|
||||||
|
('INVERSE_SQRT', 'Inverse Square Root', 'Inverse Square Root Mode'),
|
||||||
|
('EXPONENT', 'Exponent', 'Exponent Mode'),
|
||||||
|
('MINIMUM', 'Minimum', 'Minimum Mode'),
|
||||||
|
('MAXIMUM', 'Maximum', 'Maximum Mode'),
|
||||||
|
('LESS_THAN', 'Less Than', 'Less Than Mode'),
|
||||||
|
('GREATER_THAN', 'Greater Than', 'Greater Than Mode'),
|
||||||
|
('SIGN', 'Sign', 'Sign Mode'),
|
||||||
|
('COMPARE', 'Compare', 'Compare Mode'),
|
||||||
|
('SMOOTH_MIN', 'Smooth Minimum', 'Smooth Minimum Mode'),
|
||||||
|
('SMOOTH_MAX', 'Smooth Maximum', 'Smooth Maximum Mode'),
|
||||||
|
('FRACT', 'Fraction', 'Fraction Mode'),
|
||||||
|
('MODULO', 'Modulo', 'Modulo Mode'),
|
||||||
|
('SNAP', 'Snap', 'Snap Mode'),
|
||||||
|
('WRAP', 'Wrap', 'Wrap Mode'),
|
||||||
|
('PINGPONG', 'Pingpong', 'Pingpong Mode'),
|
||||||
|
('ABSOLUTE', 'Absolute', 'Absolute Mode'),
|
||||||
|
('ROUND', 'Round', 'Round Mode'),
|
||||||
|
('FLOOR', 'Floor', 'Floor Mode'),
|
||||||
|
('CEIL', 'Ceil', 'Ceil Mode'),
|
||||||
|
('TRUNCATE', 'Truncate', 'Truncate Mode'),
|
||||||
|
('RADIANS', 'To Radians', 'To Radians Mode'),
|
||||||
|
('DEGREES', 'To Degrees', 'To Degrees Mode'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Operations used by the geometry boolean node and join geometry node
|
||||||
|
geo_combine_operations = [
|
||||||
|
('JOIN', 'Join Geometry', 'Join Geometry Mode'),
|
||||||
|
('INTERSECT', 'Intersect', 'Intersect Mode'),
|
||||||
|
('UNION', 'Union', 'Union Mode'),
|
||||||
|
('DIFFERENCE', 'Difference', 'Difference Mode'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# in NWBatchChangeNodes additional types/operations. Can be used as 'items' for EnumProperty.
|
||||||
|
# used list, not tuple for easy merging with other lists.
|
||||||
|
navs = [
|
||||||
|
('CURRENT', 'Current', 'Leave at current state'),
|
||||||
|
('NEXT', 'Next', 'Next blend type/operation'),
|
||||||
|
('PREV', 'Prev', 'Previous blend type/operation'),
|
||||||
|
]
|
||||||
|
|
||||||
|
draw_color_sets = {
|
||||||
|
"red_white": (
|
||||||
|
(1.0, 1.0, 1.0, 0.7),
|
||||||
|
(1.0, 0.0, 0.0, 0.7),
|
||||||
|
(0.8, 0.2, 0.2, 1.0)
|
||||||
|
),
|
||||||
|
"green": (
|
||||||
|
(0.0, 0.0, 0.0, 1.0),
|
||||||
|
(0.38, 0.77, 0.38, 1.0),
|
||||||
|
(0.38, 0.77, 0.38, 1.0)
|
||||||
|
),
|
||||||
|
"yellow": (
|
||||||
|
(0.0, 0.0, 0.0, 1.0),
|
||||||
|
(0.77, 0.77, 0.16, 1.0),
|
||||||
|
(0.77, 0.77, 0.16, 1.0)
|
||||||
|
),
|
||||||
|
"purple": (
|
||||||
|
(0.0, 0.0, 0.0, 1.0),
|
||||||
|
(0.38, 0.38, 0.77, 1.0),
|
||||||
|
(0.38, 0.38, 0.77, 1.0)
|
||||||
|
),
|
||||||
|
"grey": (
|
||||||
|
(0.0, 0.0, 0.0, 1.0),
|
||||||
|
(0.63, 0.63, 0.63, 1.0),
|
||||||
|
(0.63, 0.63, 0.63, 1.0)
|
||||||
|
),
|
||||||
|
"black": (
|
||||||
|
(1.0, 1.0, 1.0, 0.7),
|
||||||
|
(0.0, 0.0, 0.0, 0.7),
|
||||||
|
(0.2, 0.2, 0.2, 1.0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_nodes_from_category(category_name, context):
|
||||||
|
for category in node_categories_iter(context):
|
||||||
|
if category.name == category_name:
|
||||||
|
return sorted(category.items(context), key=lambda node: node.label)
|
||||||
|
|
||||||
|
|
||||||
|
def nice_hotkey_name(punc):
|
||||||
|
# convert the ugly string name into the actual character
|
||||||
|
nice_name = {
|
||||||
|
'LEFTMOUSE': "LMB",
|
||||||
|
'MIDDLEMOUSE': "MMB",
|
||||||
|
'RIGHTMOUSE': "RMB",
|
||||||
|
'WHEELUPMOUSE': "Wheel Up",
|
||||||
|
'WHEELDOWNMOUSE': "Wheel Down",
|
||||||
|
'WHEELINMOUSE': "Wheel In",
|
||||||
|
'WHEELOUTMOUSE': "Wheel Out",
|
||||||
|
'ZERO': "0",
|
||||||
|
'ONE': "1",
|
||||||
|
'TWO': "2",
|
||||||
|
'THREE': "3",
|
||||||
|
'FOUR': "4",
|
||||||
|
'FIVE': "5",
|
||||||
|
'SIX': "6",
|
||||||
|
'SEVEN': "7",
|
||||||
|
'EIGHT': "8",
|
||||||
|
'NINE': "9",
|
||||||
|
'OSKEY': "Super",
|
||||||
|
'RET': "Enter",
|
||||||
|
'LINE_FEED': "Enter",
|
||||||
|
'SEMI_COLON': ";",
|
||||||
|
'PERIOD': ".",
|
||||||
|
'COMMA': ",",
|
||||||
|
'QUOTE': '"',
|
||||||
|
'MINUS': "-",
|
||||||
|
'SLASH': "/",
|
||||||
|
'BACK_SLASH': "\\",
|
||||||
|
'EQUAL': "=",
|
||||||
|
'NUMPAD_1': "Numpad 1",
|
||||||
|
'NUMPAD_2': "Numpad 2",
|
||||||
|
'NUMPAD_3': "Numpad 3",
|
||||||
|
'NUMPAD_4': "Numpad 4",
|
||||||
|
'NUMPAD_5': "Numpad 5",
|
||||||
|
'NUMPAD_6': "Numpad 6",
|
||||||
|
'NUMPAD_7': "Numpad 7",
|
||||||
|
'NUMPAD_8': "Numpad 8",
|
||||||
|
'NUMPAD_9': "Numpad 9",
|
||||||
|
'NUMPAD_0': "Numpad 0",
|
||||||
|
'NUMPAD_PERIOD': "Numpad .",
|
||||||
|
'NUMPAD_SLASH': "Numpad /",
|
||||||
|
'NUMPAD_ASTERIX': "Numpad *",
|
||||||
|
'NUMPAD_MINUS': "Numpad -",
|
||||||
|
'NUMPAD_ENTER': "Numpad Enter",
|
||||||
|
'NUMPAD_PLUS': "Numpad +",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
return nice_name[punc]
|
||||||
|
except KeyError:
|
||||||
|
return punc.replace("_", " ").title()
|
217
node_wrangler/utils/draw.py
Normal file
217
node_wrangler/utils/draw.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import gpu
|
||||||
|
from gpu_extras.batch import batch_for_shader
|
||||||
|
from math import cos, sin, pi
|
||||||
|
|
||||||
|
from .nodes import get_nodes_links, prefs_line_width, abs_node_location, dpi_fac
|
||||||
|
|
||||||
|
|
||||||
|
def draw_line(x1, y1, x2, y2, size, colour=(1.0, 1.0, 1.0, 0.7)):
|
||||||
|
shader = gpu.shader.from_builtin('POLYLINE_SMOOTH_COLOR')
|
||||||
|
shader.uniform_float("viewportSize", gpu.state.viewport_get()[2:])
|
||||||
|
shader.uniform_float("lineWidth", size * prefs_line_width())
|
||||||
|
|
||||||
|
vertices = ((x1, y1), (x2, y2))
|
||||||
|
vertex_colors = ((colour[0] + (1.0 - colour[0]) / 4,
|
||||||
|
colour[1] + (1.0 - colour[1]) / 4,
|
||||||
|
colour[2] + (1.0 - colour[2]) / 4,
|
||||||
|
colour[3] + (1.0 - colour[3]) / 4),
|
||||||
|
colour)
|
||||||
|
|
||||||
|
batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": vertices, "color": vertex_colors})
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_circle_2d_filled(mx, my, radius, colour=(1.0, 1.0, 1.0, 0.7)):
|
||||||
|
radius = radius * prefs_line_width()
|
||||||
|
sides = 12
|
||||||
|
vertices = [(radius * cos(i * 2 * pi / sides) + mx,
|
||||||
|
radius * sin(i * 2 * pi / sides) + my)
|
||||||
|
for i in range(sides + 1)]
|
||||||
|
|
||||||
|
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
|
||||||
|
shader.uniform_float("color", colour)
|
||||||
|
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_rounded_node_border(node, radius=8, colour=(1.0, 1.0, 1.0, 0.7)):
|
||||||
|
area_width = bpy.context.area.width
|
||||||
|
sides = 16
|
||||||
|
radius *= prefs_line_width()
|
||||||
|
|
||||||
|
nlocx, nlocy = abs_node_location(node)
|
||||||
|
|
||||||
|
nlocx = (nlocx + 1) * dpi_fac()
|
||||||
|
nlocy = (nlocy + 1) * dpi_fac()
|
||||||
|
ndimx = node.dimensions.x
|
||||||
|
ndimy = node.dimensions.y
|
||||||
|
|
||||||
|
if node.hide:
|
||||||
|
nlocx += -1
|
||||||
|
nlocy += 5
|
||||||
|
if node.type == 'REROUTE':
|
||||||
|
# nlocx += 1
|
||||||
|
nlocy -= 1
|
||||||
|
ndimx = 0
|
||||||
|
ndimy = 0
|
||||||
|
radius += 6
|
||||||
|
|
||||||
|
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
|
||||||
|
shader.uniform_float("color", colour)
|
||||||
|
|
||||||
|
# Top left corner
|
||||||
|
mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
|
||||||
|
vertices = [(mx, my)]
|
||||||
|
for i in range(sides + 1):
|
||||||
|
if (4 <= i <= 8):
|
||||||
|
if mx < area_width:
|
||||||
|
cosine = radius * cos(i * 2 * pi / sides) + mx
|
||||||
|
sine = radius * sin(i * 2 * pi / sides) + my
|
||||||
|
vertices.append((cosine, sine))
|
||||||
|
|
||||||
|
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
# Top right corner
|
||||||
|
mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
|
||||||
|
vertices = [(mx, my)]
|
||||||
|
for i in range(sides + 1):
|
||||||
|
if (0 <= i <= 4):
|
||||||
|
if mx < area_width:
|
||||||
|
cosine = radius * cos(i * 2 * pi / sides) + mx
|
||||||
|
sine = radius * sin(i * 2 * pi / sides) + my
|
||||||
|
vertices.append((cosine, sine))
|
||||||
|
|
||||||
|
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
# Bottom left corner
|
||||||
|
mx, my = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
|
||||||
|
vertices = [(mx, my)]
|
||||||
|
for i in range(sides + 1):
|
||||||
|
if (8 <= i <= 12):
|
||||||
|
if mx < area_width:
|
||||||
|
cosine = radius * cos(i * 2 * pi / sides) + mx
|
||||||
|
sine = radius * sin(i * 2 * pi / sides) + my
|
||||||
|
vertices.append((cosine, sine))
|
||||||
|
|
||||||
|
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
# Bottom right corner
|
||||||
|
mx, my = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
|
||||||
|
vertices = [(mx, my)]
|
||||||
|
for i in range(sides + 1):
|
||||||
|
if (12 <= i <= 16):
|
||||||
|
if mx < area_width:
|
||||||
|
cosine = radius * cos(i * 2 * pi / sides) + mx
|
||||||
|
sine = radius * sin(i * 2 * pi / sides) + my
|
||||||
|
vertices.append((cosine, sine))
|
||||||
|
|
||||||
|
batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
# prepare drawing all edges in one batch
|
||||||
|
vertices = []
|
||||||
|
indices = []
|
||||||
|
id_last = 0
|
||||||
|
|
||||||
|
# Left edge
|
||||||
|
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
|
||||||
|
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
|
||||||
|
if m1x < area_width and m2x < area_width:
|
||||||
|
vertices.extend([(m2x - radius, m2y), (m2x, m2y),
|
||||||
|
(m1x, m1y), (m1x - radius, m1y)])
|
||||||
|
indices.extend([(id_last, id_last + 1, id_last + 3),
|
||||||
|
(id_last + 3, id_last + 1, id_last + 2)])
|
||||||
|
id_last += 4
|
||||||
|
|
||||||
|
# Top edge
|
||||||
|
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy, clip=False)
|
||||||
|
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
|
||||||
|
m1x = min(m1x, area_width)
|
||||||
|
m2x = min(m2x, area_width)
|
||||||
|
vertices.extend([(m1x, m1y), (m2x, m1y),
|
||||||
|
(m2x, m1y + radius), (m1x, m1y + radius)])
|
||||||
|
indices.extend([(id_last, id_last + 1, id_last + 3),
|
||||||
|
(id_last + 3, id_last + 1, id_last + 2)])
|
||||||
|
id_last += 4
|
||||||
|
|
||||||
|
# Right edge
|
||||||
|
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy, clip=False)
|
||||||
|
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
|
||||||
|
if m1x < area_width and m2x < area_width:
|
||||||
|
vertices.extend([(m1x, m2y), (m1x + radius, m2y),
|
||||||
|
(m1x + radius, m1y), (m1x, m1y)])
|
||||||
|
indices.extend([(id_last, id_last + 1, id_last + 3),
|
||||||
|
(id_last + 3, id_last + 1, id_last + 2)])
|
||||||
|
id_last += 4
|
||||||
|
|
||||||
|
# Bottom edge
|
||||||
|
m1x, m1y = bpy.context.region.view2d.view_to_region(nlocx, nlocy - ndimy, clip=False)
|
||||||
|
m2x, m2y = bpy.context.region.view2d.view_to_region(nlocx + ndimx, nlocy - ndimy, clip=False)
|
||||||
|
m1x = min(m1x, area_width)
|
||||||
|
m2x = min(m2x, area_width)
|
||||||
|
vertices.extend([(m1x, m2y), (m2x, m2y),
|
||||||
|
(m2x, m1y - radius), (m1x, m1y - radius)])
|
||||||
|
indices.extend([(id_last, id_last + 1, id_last + 3),
|
||||||
|
(id_last + 3, id_last + 1, id_last + 2)])
|
||||||
|
|
||||||
|
# now draw all edges in one batch
|
||||||
|
if len(vertices) != 0:
|
||||||
|
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_callback_nodeoutline(self, context, mode):
|
||||||
|
if self.mouse_path:
|
||||||
|
gpu.state.blend_set('ALPHA')
|
||||||
|
|
||||||
|
nodes, _links = get_nodes_links(context)
|
||||||
|
|
||||||
|
if mode == "LINK":
|
||||||
|
col_outer = (1.0, 0.2, 0.2, 0.4)
|
||||||
|
col_inner = (0.0, 0.0, 0.0, 0.5)
|
||||||
|
col_circle_inner = (0.3, 0.05, 0.05, 1.0)
|
||||||
|
elif mode == "LINKMENU":
|
||||||
|
col_outer = (0.4, 0.6, 1.0, 0.4)
|
||||||
|
col_inner = (0.0, 0.0, 0.0, 0.5)
|
||||||
|
col_circle_inner = (0.08, 0.15, .3, 1.0)
|
||||||
|
elif mode == "MIX":
|
||||||
|
col_outer = (0.2, 1.0, 0.2, 0.4)
|
||||||
|
col_inner = (0.0, 0.0, 0.0, 0.5)
|
||||||
|
col_circle_inner = (0.05, 0.3, 0.05, 1.0)
|
||||||
|
|
||||||
|
m1x = self.mouse_path[0][0]
|
||||||
|
m1y = self.mouse_path[0][1]
|
||||||
|
m2x = self.mouse_path[-1][0]
|
||||||
|
m2y = self.mouse_path[-1][1]
|
||||||
|
|
||||||
|
n1 = nodes[context.scene.NWLazySource]
|
||||||
|
n2 = nodes[context.scene.NWLazyTarget]
|
||||||
|
|
||||||
|
if n1 == n2:
|
||||||
|
col_outer = (0.4, 0.4, 0.4, 0.4)
|
||||||
|
col_inner = (0.0, 0.0, 0.0, 0.5)
|
||||||
|
col_circle_inner = (0.2, 0.2, 0.2, 1.0)
|
||||||
|
|
||||||
|
draw_rounded_node_border(n1, radius=6, colour=col_outer) # outline
|
||||||
|
draw_rounded_node_border(n1, radius=5, colour=col_inner) # inner
|
||||||
|
draw_rounded_node_border(n2, radius=6, colour=col_outer) # outline
|
||||||
|
draw_rounded_node_border(n2, radius=5, colour=col_inner) # inner
|
||||||
|
|
||||||
|
draw_line(m1x, m1y, m2x, m2y, 5, col_outer) # line outline
|
||||||
|
draw_line(m1x, m1y, m2x, m2y, 2, col_inner) # line inner
|
||||||
|
|
||||||
|
# circle outline
|
||||||
|
draw_circle_2d_filled(m1x, m1y, 7, col_outer)
|
||||||
|
draw_circle_2d_filled(m2x, m2y, 7, col_outer)
|
||||||
|
|
||||||
|
# circle inner
|
||||||
|
draw_circle_2d_filled(m1x, m1y, 5, col_circle_inner)
|
||||||
|
draw_circle_2d_filled(m2x, m2y, 5, col_circle_inner)
|
||||||
|
|
||||||
|
gpu.state.blend_set('NONE')
|
256
node_wrangler/utils/nodes.py
Normal file
256
node_wrangler/utils/nodes.py
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from math import hypot
|
||||||
|
|
||||||
|
|
||||||
|
def force_update(context):
|
||||||
|
context.space_data.node_tree.update_tag()
|
||||||
|
|
||||||
|
|
||||||
|
def dpi_fac():
|
||||||
|
prefs = bpy.context.preferences.system
|
||||||
|
return prefs.dpi / 72
|
||||||
|
|
||||||
|
|
||||||
|
def prefs_line_width():
|
||||||
|
prefs = bpy.context.preferences.system
|
||||||
|
return prefs.pixel_size
|
||||||
|
|
||||||
|
|
||||||
|
def node_mid_pt(node, axis):
|
||||||
|
if axis == 'x':
|
||||||
|
d = node.location.x + (node.dimensions.x / 2)
|
||||||
|
elif axis == 'y':
|
||||||
|
d = node.location.y - (node.dimensions.y / 2)
|
||||||
|
else:
|
||||||
|
d = 0
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def autolink(node1, node2, links):
|
||||||
|
link_made = False
|
||||||
|
available_inputs = [inp for inp in node2.inputs if inp.enabled]
|
||||||
|
available_outputs = [outp for outp in node1.outputs if outp.enabled]
|
||||||
|
for outp in available_outputs:
|
||||||
|
for inp in available_inputs:
|
||||||
|
if not inp.is_linked and inp.name == outp.name:
|
||||||
|
link_made = True
|
||||||
|
links.new(outp, inp)
|
||||||
|
return True
|
||||||
|
|
||||||
|
for outp in available_outputs:
|
||||||
|
for inp in available_inputs:
|
||||||
|
if not inp.is_linked and inp.type == outp.type:
|
||||||
|
link_made = True
|
||||||
|
links.new(outp, inp)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# force some connection even if the type doesn't match
|
||||||
|
if available_outputs:
|
||||||
|
for inp in available_inputs:
|
||||||
|
if not inp.is_linked:
|
||||||
|
link_made = True
|
||||||
|
links.new(available_outputs[0], inp)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# even if no sockets are open, force one of matching type
|
||||||
|
for outp in available_outputs:
|
||||||
|
for inp in available_inputs:
|
||||||
|
if inp.type == outp.type:
|
||||||
|
link_made = True
|
||||||
|
links.new(outp, inp)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# do something!
|
||||||
|
for outp in available_outputs:
|
||||||
|
for inp in available_inputs:
|
||||||
|
link_made = True
|
||||||
|
links.new(outp, inp)
|
||||||
|
return True
|
||||||
|
|
||||||
|
print("Could not make a link from " + node1.name + " to " + node2.name)
|
||||||
|
return link_made
|
||||||
|
|
||||||
|
|
||||||
|
def abs_node_location(node):
|
||||||
|
abs_location = node.location
|
||||||
|
if node.parent is None:
|
||||||
|
return abs_location
|
||||||
|
return abs_location + abs_node_location(node.parent)
|
||||||
|
|
||||||
|
|
||||||
|
def node_at_pos(nodes, context, event):
|
||||||
|
nodes_under_mouse = []
|
||||||
|
target_node = None
|
||||||
|
|
||||||
|
store_mouse_cursor(context, event)
|
||||||
|
x, y = context.space_data.cursor_location
|
||||||
|
|
||||||
|
# Make a list of each corner (and middle of border) for each node.
|
||||||
|
# Will be sorted to find nearest point and thus nearest node
|
||||||
|
node_points_with_dist = []
|
||||||
|
for node in nodes:
|
||||||
|
skipnode = False
|
||||||
|
if node.type != 'FRAME': # no point trying to link to a frame node
|
||||||
|
dimx = node.dimensions.x / dpi_fac()
|
||||||
|
dimy = node.dimensions.y / dpi_fac()
|
||||||
|
locx, locy = abs_node_location(node)
|
||||||
|
|
||||||
|
if not skipnode:
|
||||||
|
node_points_with_dist.append([node, hypot(x - locx, y - locy)]) # Top Left
|
||||||
|
node_points_with_dist.append([node, hypot(x - (locx + dimx), y - locy)]) # Top Right
|
||||||
|
node_points_with_dist.append([node, hypot(x - locx, y - (locy - dimy))]) # Bottom Left
|
||||||
|
node_points_with_dist.append([node, hypot(x - (locx + dimx), y - (locy - dimy))]) # Bottom Right
|
||||||
|
|
||||||
|
node_points_with_dist.append([node, hypot(x - (locx + (dimx / 2)), y - locy)]) # Mid Top
|
||||||
|
node_points_with_dist.append([node, hypot(x - (locx + (dimx / 2)), y - (locy - dimy))]) # Mid Bottom
|
||||||
|
node_points_with_dist.append([node, hypot(x - locx, y - (locy - (dimy / 2)))]) # Mid Left
|
||||||
|
node_points_with_dist.append([node, hypot(x - (locx + dimx), y - (locy - (dimy / 2)))]) # Mid Right
|
||||||
|
|
||||||
|
nearest_node = sorted(node_points_with_dist, key=lambda k: k[1])[0][0]
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
if node.type != 'FRAME' and skipnode == False:
|
||||||
|
locx, locy = abs_node_location(node)
|
||||||
|
dimx = node.dimensions.x / dpi_fac()
|
||||||
|
dimy = node.dimensions.y / dpi_fac()
|
||||||
|
if (locx <= x <= locx + dimx) and \
|
||||||
|
(locy - dimy <= y <= locy):
|
||||||
|
nodes_under_mouse.append(node)
|
||||||
|
|
||||||
|
if len(nodes_under_mouse) == 1:
|
||||||
|
if nodes_under_mouse[0] != nearest_node:
|
||||||
|
target_node = nodes_under_mouse[0] # use the node under the mouse if there is one and only one
|
||||||
|
else:
|
||||||
|
target_node = nearest_node # else use the nearest node
|
||||||
|
else:
|
||||||
|
target_node = nearest_node
|
||||||
|
return target_node
|
||||||
|
|
||||||
|
|
||||||
|
def store_mouse_cursor(context, event):
|
||||||
|
space = context.space_data
|
||||||
|
v2d = context.region.view2d
|
||||||
|
tree = space.edit_tree
|
||||||
|
|
||||||
|
# convert mouse position to the View2D for later node placement
|
||||||
|
if context.region.type == 'WINDOW':
|
||||||
|
space.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y)
|
||||||
|
else:
|
||||||
|
space.cursor_location = tree.view_center
|
||||||
|
|
||||||
|
|
||||||
|
def get_active_tree(context):
|
||||||
|
tree = context.space_data.node_tree
|
||||||
|
path = []
|
||||||
|
# Get nodes from currently edited tree.
|
||||||
|
# If user is editing a group, space_data.node_tree is still the base level (outside group).
|
||||||
|
# context.active_node is in the group though, so if space_data.node_tree.nodes.active is not
|
||||||
|
# the same as context.active_node, the user is in a group.
|
||||||
|
# Check recursively until we find the real active node_tree:
|
||||||
|
if tree.nodes.active:
|
||||||
|
while tree.nodes.active != context.active_node:
|
||||||
|
tree = tree.nodes.active.node_tree
|
||||||
|
path.append(tree)
|
||||||
|
return tree, path
|
||||||
|
|
||||||
|
|
||||||
|
def get_nodes_links(context):
|
||||||
|
tree, path = get_active_tree(context)
|
||||||
|
return tree.nodes, tree.links
|
||||||
|
|
||||||
|
|
||||||
|
viewer_socket_name = "tmp_viewer"
|
||||||
|
|
||||||
|
|
||||||
|
def is_viewer_socket(socket):
|
||||||
|
# checks if a internal socket is a valid viewer socket
|
||||||
|
return socket.name == viewer_socket_name and socket.NWViewerSocket
|
||||||
|
|
||||||
|
|
||||||
|
def get_internal_socket(socket):
|
||||||
|
# get the internal socket from a socket inside or outside the group
|
||||||
|
node = socket.node
|
||||||
|
if node.type == 'GROUP_OUTPUT':
|
||||||
|
source_iterator = node.inputs
|
||||||
|
iterator = node.id_data.outputs
|
||||||
|
elif node.type == 'GROUP_INPUT':
|
||||||
|
source_iterator = node.outputs
|
||||||
|
iterator = node.id_data.inputs
|
||||||
|
elif hasattr(node, "node_tree"):
|
||||||
|
if socket.is_output:
|
||||||
|
source_iterator = node.outputs
|
||||||
|
iterator = node.node_tree.outputs
|
||||||
|
else:
|
||||||
|
source_iterator = node.inputs
|
||||||
|
iterator = node.node_tree.inputs
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for i, s in enumerate(source_iterator):
|
||||||
|
if s == socket:
|
||||||
|
break
|
||||||
|
return iterator[i]
|
||||||
|
|
||||||
|
|
||||||
|
def is_viewer_link(link, output_node):
|
||||||
|
if link.to_node == output_node and link.to_socket == output_node.inputs[0]:
|
||||||
|
return True
|
||||||
|
if link.to_node.type == 'GROUP_OUTPUT':
|
||||||
|
socket = get_internal_socket(link.to_socket)
|
||||||
|
if is_viewer_socket(socket):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_group_output_node(tree):
|
||||||
|
for node in tree.nodes:
|
||||||
|
if node.type == 'GROUP_OUTPUT' and node.is_active_output:
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
def get_output_location(tree):
|
||||||
|
# get right-most location
|
||||||
|
sorted_by_xloc = (sorted(tree.nodes, key=lambda x: x.location.x))
|
||||||
|
max_xloc_node = sorted_by_xloc[-1]
|
||||||
|
|
||||||
|
# get average y location
|
||||||
|
sum_yloc = 0
|
||||||
|
for node in tree.nodes:
|
||||||
|
sum_yloc += node.location.y
|
||||||
|
|
||||||
|
loc_x = max_xloc_node.location.x + max_xloc_node.dimensions.x + 80
|
||||||
|
loc_y = sum_yloc / len(tree.nodes)
|
||||||
|
return loc_x, loc_y
|
||||||
|
|
||||||
|
|
||||||
|
def nw_check(context):
|
||||||
|
space = context.space_data
|
||||||
|
valid_trees = ["ShaderNodeTree", "CompositorNodeTree", "TextureNodeTree", "GeometryNodeTree"]
|
||||||
|
|
||||||
|
if (space.type == 'NODE_EDITOR'
|
||||||
|
and space.node_tree is not None
|
||||||
|
and space.node_tree.library is None
|
||||||
|
and space.tree_type in valid_trees):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_first_enabled_output(node):
|
||||||
|
for output in node.outputs:
|
||||||
|
if output.enabled:
|
||||||
|
return output
|
||||||
|
else:
|
||||||
|
return node.outputs[0]
|
||||||
|
|
||||||
|
|
||||||
|
def is_visible_socket(socket):
|
||||||
|
return not socket.hide and socket.enabled and socket.type != 'CUSTOM'
|
||||||
|
|
||||||
|
|
||||||
|
class NWBase:
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return nw_check(context)
|
@ -10,9 +10,9 @@ from dataclasses import dataclass
|
|||||||
# XXX Not really nice, but that hack is needed to allow execution of that test
|
# XXX Not really nice, but that hack is needed to allow execution of that test
|
||||||
# from both automated CTest and by directly running the file manually.
|
# from both automated CTest and by directly running the file manually.
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from util import match_files_to_socket_names
|
from paths import match_files_to_socket_names
|
||||||
else:
|
else:
|
||||||
from .util import match_files_to_socket_names
|
from .paths import match_files_to_socket_names
|
||||||
|
|
||||||
|
|
||||||
# From NWPrincipledPreferences 2023-01-06
|
# From NWPrincipledPreferences 2023-01-06
|
@ -606,7 +606,7 @@ def main(context):
|
|||||||
ob.scale = oldScale
|
ob.scale = oldScale
|
||||||
ob.select_set(False)
|
ob.select_set(False)
|
||||||
armature_object.select_set(True)
|
armature_object.select_set(True)
|
||||||
scn.objects.active = armature_object
|
context.view_layer.objects.active = armature_object
|
||||||
|
|
||||||
armature_object.location = oldLocation
|
armature_object.location = oldLocation
|
||||||
armature_object.rotation_euler = oldRotation
|
armature_object.rotation_euler = oldRotation
|
||||||
|
Loading…
Reference in New Issue
Block a user