| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867 |
- from .node_container_common import *
- from .xForm_nodes import xFormGeometryObject, xFormObjectInstance
- from .misc_nodes import InputExistingGeometryObject
- from .base_definitions import MantisNode, MantisSocketTemplate
- from .utilities import (prRed, prGreen, prPurple, prWhite, prOrange,
- wrapRed, wrapGreen, wrapPurple, wrapWhite,
- wrapOrange,)
- from .deformer_socket_templates import *
- from bpy.types import NodeTree
- def TellClasses():
-
- return [
- DeformerArmature,
- DeformerHook,
- DeformerMorphTarget,
- DeformerMorphTargetDeform,
- DeformerSurfaceDeform,
- DeformerMeshDeform,
- DeformerLatticeDeform,
- DeformerSmoothCorrectiveDeform,
- ]
- # object instance probably can't use the deformer but it doesn't hurt to try.
- deformable_types= (xFormGeometryObject, InputExistingGeometryObject, xFormObjectInstance)
- def trace_xForm_back(nc, socket):
- if (trace := trace_single_line(nc, socket)[0] ) :
- for i in range(len(trace)): # have to look in reverse, actually
- if ( isinstance(trace[ i ], deformable_types ) ):
- return trace[ i ].bGetObject()
- raise GraphError(wrapRed(f"No other object found for {nc}."))
- class MantisDeformerNode(MantisNode):
- def __init__(self, signature : tuple,
- base_tree : NodeTree,
- socket_templates : list[MantisSocketTemplate]=[]):
- super().__init__(signature, base_tree, socket_templates)
- self.node_type = 'LINK'
- self.prepared = True
- self.bObject=[]
- # we need evaluate_input to have the same behaviour as links.
- def evaluate_input(self, input_name, index=0):
- if (input_name in ['Target', 'Object', 'Hook Target']):
- socket = self.inputs.get(input_name)
- if socket.is_linked:
- return socket.links[0].from_node
- return None
-
- else:
- return super().evaluate_input(input_name, index)
-
- def GetxForm(nc, output_name="Deformer"):
- break_condition= lambda node : node.__class__ in deformable_types
- xforms = trace_line_up_branching(nc, output_name, break_condition)
- return_me=[]
- for xf in xforms:
- if xf.node_type != 'XFORM':
- continue
- if xf in return_me:
- continue
- return_me.append(xf)
- return return_me
-
- def reset_execution(self):
- super().reset_execution()
- self.bObject=[]; self.prepared=True
- def standard_modifier_bind(self, bContext=None, operator=None):
- for d in self.bObject:
- # we'll only bind it if it is un-muted.
- if self.evaluate_input("Enable in Viewport") == False:
- continue
- from .utilities import bind_modifier_operator
- bind_modifier_operator(d, operator)
- class DeformerArmature(MantisDeformerNode):
- '''A node representing an armature deformer'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Input Relationship",
- "Armature Object",
- "Blend Vertex Group",
- "Invert Vertex Group",
- "Preserve Volume",
- "Use Multi Modifier",
- "Use Envelopes",
- "Use Vertex Groups",
- "Skinning Method",
- "Deformer",
- "Copy Skin Weights From"
- ]
- outputs = [
- "Deformer"
- ]
- self.outputs.init_sockets(outputs)
- self.inputs.init_sockets(inputs)
- self.init_parameters(additional_parameters={"Name":None})
- self.set_traverse([("Deformer", "Deformer")])
- self.node_type = "LINK"
- self.prepared = True
- def GetxForm(self, socket="Deformer"):
- if socket == "Deformer":
- return super().GetxForm()
- else:
- trace_xForm_back(self, socket)
-
- # DUPLICATED FROM xForm_nodes::xFormBone
- # DEDUP HACK HACK HACK HACK HACK
- def bGetParentArmature(self):
- from .xForm_nodes import xFormArmature
- from .misc_nodes import InputExistingGeometryObject
- from bpy.types import Object
- if (trace := trace_single_line(self, "Armature Object")[0] ) :
- for i in range(len(trace)):
- # have to look in reverse, actually
- if ( isinstance(trace[ i ], xFormArmature ) ):
- return trace[ i ].bGetObject()
- elif ( isinstance(trace[i], InputExistingGeometryObject)):
- if (ob := trace[i].bGetObject()).type == "ARMATURE":
- return ob
- raise RuntimeError(f"Cannot find armature for node {self}")
- return None
- #should do the trick...
- def bRelationshipPass(self, bContext = None,):
- self.executed = True
-
- def initialize_vgroups(self, xf):
- ob = xf.bGetObject()
- armOb = self.bGetParentArmature()
- for b in armOb.data.bones:
- if b.use_deform == False:
- continue
- vg = ob.vertex_groups.get(b.name)
- if not vg:
- vg = ob.vertex_groups.new(name=b.name)
- if ob.type == 'MESH':
- num_verts = len(ob.data.vertices)
- elif ob.type == 'LATTICE':
- num_verts = len(ob.data.points)
- vg.add(range(num_verts), 0, 'REPLACE')
-
- def copy_weights(self, xf):
- # we'll use modifiers for this, maybe use GN for it in the future tho
- import bpy
- ob = xf.bGetObject()
- try:
- copy_from = self.GetxForm(socket="Copy Skin Weights From")
- except GraphError:
- copy_from = None
- prRed(f"No object found for copying weights in {self}, continuing anyway.")
- m = ob.modifiers.new(type="DATA_TRANSFER", name="Mantis_temp_data_transfer")
- m.object = None; m.use_vert_data = True
- m.data_types_verts = {'VGROUP_WEIGHTS'}
- m.vert_mapping = 'POLYINTERP_NEAREST'
- m.layers_vgroup_select_src = 'ALL'
- m.layers_vgroup_select_dst = 'NAME'
- m.object = copy_from
- # m.use_object_transform = False # testing reveals that this is undesirable - since the objects may not have their transforms applied.
- ob.modifiers.move(len(ob.modifiers)-1, 0)
- # ob.data = ob.data.copy()
- if False: #MAYBE the mouse needs to be in the 3D viewport, no idea how to set this in an override
- # TODO: figure out how to apply this, context is incorrect because armature is still in pose mode
- original_active = bpy.context.active_object
- original_mode = original_active.mode
- bpy.ops.object.mode_set(mode='OBJECT')
- with bpy.context.temp_override(**{'active_object':ob, 'selected_objects':[ob, copy_from]}):
- # bpy.ops.object.datalayout_transfer(modifier=m.name) # note: this operator is used by the modifier or stand-alone in the UI
- # the poll for this operator is defined in blender/source/blender/editors/object/object_data_transfer.cc
- # and blender/source/blender/editors/object/object_modifier.cc
- # bpy.ops.object.modifier_apply(modifier=m.name, single_user=True)
- bpy.ops.object.datalayout_transfer(data_type='VGROUP_WEIGHTS')
- bpy.ops.object.data_transfer(data_type='VGROUP_WEIGHTS')
- bpy.ops.object.mode_set(mode=original_mode)
-
- def do_automatic_skinning_mesh(self, ob, xf, bContext):
- # This is bad and leads to somewhat unpredictable
- # behaviour, e.g. what object will be selected? What mode?
- # also bpy.ops is ugly and prone to error when used in
- # scripts. I don't intend to use bpy.ops when I can avoid it.
- import bpy
- self.initialize_vgroups(xf)
- armOb = self.bGetParentArmature()
- armOb.data.pose_position = 'REST'
- bContext.view_layer.depsgraph.update()
- deform_bones = []
- for pb in armOb.pose.bones:
- if pb.bone.use_deform == True:
- deform_bones.append(pb)
- if not deform_bones:
- prPurple("Warning: No deform bones in armature. Cancelling.")
- return
- context_override = {
- 'active_object':ob,
- 'selected_objects':[ob, armOb],
- 'active_pose_bone':deform_bones[0],
- 'selected_pose_bones':deform_bones,}
- for b in armOb.data.bones:
- b.select = True
- with bContext.temp_override(**context_override):
- bpy.ops.paint.weight_paint_toggle()
- bpy.ops.paint.weight_from_bones(type='AUTOMATIC')
- bpy.ops.paint.weight_paint_toggle()
- for b in armOb.data.bones:
- b.select = False
- armOb.data.pose_position = 'POSE'
- # TODO: modify Blender to make this available as a Python API function.
- def do_automatic_skinning_lattice(self, ob, xf, bContext):
- # Temporarily, I am making a very simple and ugly automatic skinning algo for lattice points
- import bpy
- from mathutils.geometry import intersect_point_line
- self.initialize_vgroups(xf)
- armOb = self.bGetParentArmature()
- armOb.data.pose_position = 'REST'
- bContext.view_layer.depsgraph.update()
- deform_bones = []
- for pb in armOb.pose.bones:
- if pb.bone.use_deform == True: deform_bones.append(pb)
- # How this works:
- # - Calculates the weights based on proximity and angle
- # - we'll make a vector of the point and the nearest point on the bone
- # - dot (point_displacement, bone_y_axis) to get the angle
- # - weight the bone's value by this dot product and distance
- # - distance should prevail when both bones are within the angle
- mat = ob.matrix_world; mat_arm = armOb.matrix_world
- for p_index, p in enumerate(ob.data.points):
- loc = mat @ p.co_deform # co_deform is the position in edit mode
- pt_distance, pt_dot = {}, {}
- for b in deform_bones:
- bone_vec = ((mat_arm @ b.tail) - (mat_arm @ b.head)).normalized()
- nearest_point_on_bone, factor = intersect_point_line(
- loc, mat_arm @ b.head, mat_arm @ b.tail) # 0 is point, 1 is factor
- if factor > 1.0: nearest_point_on_bone = mat_arm @ b.tail
- if factor < 0.0: nearest_point_on_bone = mat_arm @ b.head
- point_vec = nearest_point_on_bone - loc
- distance = point_vec.length_squared # no need to sqrt, this is faster and
- # the quadratic falloff is better than linear falloff.
- dot = 1-abs(point_vec.normalized().dot(bone_vec))
- # we want to weight zero at 1.0 so that it favors points in its "envelope"
- pt_distance[b.name]=distance; pt_dot[b.name] = dot
- # now we can assign weights
- distance_pairs = [(k,v) for k,v in pt_distance.items()]
- distance_pairs.sort(key = lambda a : a[1])
- i=0; max_distance = 0.0; near_enough_bones = []
- while (i < 4): # TODO: limit-total should be exposed to the user.
- if i+1 > len(distance_pairs): break # in case there are fewer than 4 deform bones
- near_enough_bones.append(distance_pairs[i][0])
- if distance_pairs[i][1] > max_distance: max_distance = distance_pairs[i][1]
- i+=1
- max_pre_normalized_weight = 0.0
- weights = {}
- if max_distance == 0.0: max_distance = 1.0
- for b_name in near_enough_bones:
- w = 1.0
- if pt_distance[b_name] > 0:
- w*= 1/(pt_distance[b_name]/max_distance) # weight by inverse-distance
- w*= pt_dot[b_name]**4 # NOTE: **4 is arbitrary but feels good to me.
- if w > max_pre_normalized_weight: max_pre_normalized_weight = w
- weights[b_name] = w
- if max_pre_normalized_weight == 0.0: max_pre_normalized_weight = 1.0
- for b_name in near_enough_bones:
- vg = ob.vertex_groups.get(b_name)
- vg.add([p_index], weights[b_name]/max_pre_normalized_weight, 'REPLACE')
- armOb.data.pose_position = 'POSE'
-
- def bFinalize(self, bContext=None):
- prGreen("Executing Armature Deform Node")
- mod_name = self.evaluate_input("Name")
- for xf in self.GetxForm():
- ob = xf.bGetObject()
- d = ob.modifiers.new(mod_name, type='ARMATURE')
- if d is None:
- raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
- self.bObject.append(d)
- d.object = self.bGetParentArmature()
- props_sockets = {
- 'vertex_group' : ("Blend Vertex Group", ""),
- 'invert_vertex_group' : ("Invert Vertex Group", ""),
- 'use_deform_preserve_volume' : ("Preserve Volume", False),
- 'use_multi_modifier' : ("Use Multi Modifier", False),
- 'use_bone_envelopes' : ("Use Envelopes", False),
- 'use_vertex_groups' : ("Use Vertex Groups", False),
- }
- evaluate_sockets(self, d, props_sockets)
- #
- if (skin_method := self.evaluate_input("Skinning Method")) == "AUTOMATIC_HEAT":
- match ob.type:
- case "MESH":
- self.do_automatic_skinning_mesh(ob, xf, bContext)
- case "LATTICE":
- self.do_automatic_skinning_lattice(ob, xf, bContext)
- elif skin_method == "COPY_FROM_OBJECT":
- self.initialize_vgroups(xf)
- self.copy_weights(xf)
- # elif skin_method == "EXISTING_GROUPS":
- # pass
- class DeformerHook(MantisDeformerNode):
- '''A node representing a hook deformer'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, HookSockets)
- # now set up the traverse target...
- self.init_parameters(additional_parameters={"Name":None})
- self.set_traverse([("Deformer", "Deformer")])
- self.prepared = True
- def driver_for_radius(self, object, hook, index, influence, bezier=True):
- """ Creates a driver to control the radius of a curve point with the hook."""
- from bpy.types import Bone, PoseBone
- var_template = {"owner":hook,
- "name":"a",
- "type":"TRANSFORMS",
- "space":'WORLD_SPACE',
- "channel":'SCALE_X',}
- var1_template = {"owner":hook.id_data,
- "name":"b",
- "type":"TRANSFORMS",
- "space":'WORLD_SPACE',
- "channel":'SCALE_X',}
- keys_template = [{"co":(0,0),
- "interpolation": "LINEAR",
- "type":"KEYFRAME",},
- {"co":(1,influence),
- "interpolation": "LINEAR",
- "type":"KEYFRAME",},]
- if bezier:
- owner=object.data.splines[0].bezier_points
- else:
- owner=object.data.splines[0].points
- driver = {
- "owner":owner[index],
- "prop":"radius",
- "ind":-1,
- "extrapolation":"LINEAR",
- "type":"AVERAGE",
- "vars":[],
- "keys":keys_template,
- }
- if isinstance(hook, (Bone, PoseBone)):
- driver['type']='SCRIPTED'
- driver['expression']="(((1/b)*a)+((1/b_001)*a_001)+((1/b_002)*a_002))/3"
- from .drivers import CreateDrivers
- axes='XYZ'
- for i in range(3):
- var = var_template.copy()
- var["channel"]="SCALE_"+axes[i]
- driver["vars"].append(var)
- if isinstance(hook, (Bone, PoseBone)):
- var1=var1_template.copy()
- var1['channel']="SCALE_"+axes[i]
- driver['vars'].append(var1)
- CreateDrivers([driver])
-
- def bRelationshipPass(self, bContext = None,):
- self.executed = True
- def bFinalize(self, bContext=None):
- from bpy.types import Bone, PoseBone, Object
- prGreen(f"Executing Hook Deform Node: {self}")
- mod_name = self.evaluate_input("Name")
- affect_radius = self.evaluate_input("Affect Curve Radius")
- auto_bezier = self.evaluate_input("Auto-Bezier")
- target_node = self.evaluate_input('Hook Target')
- target = target_node.bGetObject(); subtarget = ""
- props_sockets = self.gen_property_socket_map()
- if isinstance(target, Bone) or isinstance(target, PoseBone):
- subtarget = target.name; target = target.id_data
- for xf in self.GetxForm():
- ob=xf.bGetObject()
- if ob.type == 'CURVE':
- spline_index = self.evaluate_input("Spline Index")
- from .utilities import get_extracted_spline_object
- ob = get_extracted_spline_object(ob, spline_index, self.mContext)
-
- reuse = False
- for m in ob.modifiers:
- if m.type == 'HOOK' and m.object == target and m.subtarget == subtarget:
- if self.evaluate_input("Influence") != m.strength:
- continue # make a new modifier so they can have different strengths
- if ob.animation_data: # this can be None
- drivers = ob.animation_data.drivers
- for k in props_sockets.keys():
- if driver := drivers.find(k):
- # TODO: I should check to see if the drivers are the same...
- break # continue searching for an equivalent modifier
- else: # There was no driver - use this one.
- d = m; reuse = True; break
- else: # use this one, there can't be drivers without animation_data.
- d = m; reuse = True; break
- else:
- d = ob.modifiers.new(mod_name, type='HOOK')
- if d is None:
- raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
- self.bObject.append(d)
- self.get_target_and_subtarget(d, input_name="Hook Target")
- vertices_used=[]
- if reuse: # Get the verts in the list... filter out all the unneeded 0's
- vertices_used = list(d.vertex_indices)
- include_0 = 0 in vertices_used
- vertices_used = list(filter(lambda a : a != 0, vertices_used))
- if include_0: vertices_used.append(0)
- # now we add the selected vertex to the list, too
- vertex = self.evaluate_input("Point Index")
- if ob.type == 'CURVE' and ob.data.splines[0].type == 'BEZIER' and auto_bezier:
- if affect_radius:
- self.driver_for_radius(ob, target_node.bGetObject(), vertex, d.strength)
- vertex*=3
- vertices_used.extend([vertex, vertex+1, vertex+2])
- else:
- vertices_used.append(vertex)
- # if we have a curve and it is NOT using auto-bezier for the verts..
- if ob.type == 'CURVE' and ob.data.splines[0].type == 'BEZIER' and affect_radius and not auto_bezier:
- print (f"WARN: {self}: \"Affect Radius\" may not behave as expected"
- " when used on Bezier curves without Auto-Bezier")
- #bezier point starts at 1, and then every third vert, so 4, 7, 10...
- if vertex%3==1:
- self.driver_for_radius(ob, target_node.bGetObject(), vertex, d.strength)
- if ob.type == 'CURVE' and ob.data.splines[0].type != 'BEZIER' and \
- affect_radius:
- self.driver_for_radius(ob, target_node.bGetObject(), vertex, d.strength, bezier=False)
-
- d.vertex_indices_set(vertices_used)
- evaluate_sockets(self, d, props_sockets)
- finish_drivers(self)
- # todo: this node should be able to take many indices in the future.
- # Also: I have a Geometry Nodes implementation of this I can use... maybe...
- class DeformerMorphTarget(MantisDeformerNode):
- '''A node representing an armature deformer'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Relative to",
- "Object",
- "Deformer",
- "Vertex Group",
- ]
- outputs = [
- "Deformer",
- "Morph Target",
- ]
- # now set up the traverse target...
- self.outputs.init_sockets(outputs)
- self.inputs.init_sockets(inputs)
- self.init_parameters(additional_parameters={"Name":None})
- self.set_traverse([("Deformer", "Deformer")])
- self.node_type = "LINK"
- self.prepared = True
-
- def GetxForm(self, trace_input="Object"):
- trace = trace_single_line(self, trace_input)
- for node in trace[0]:
- if (isinstance(node, deformable_types)):
- return node
- raise GraphError("%s is not connected to an upstream xForm" % self)
- def bRelationshipPass(self, bContext = None,):
- prGreen("Executing Morph Target Node")
- ob = None; relative = None
- # do NOT check if the object exists here. Just let the next node deal with that.
- try:
- ob = self.GetxForm().bGetObject().name
- except Exception as e: # this will and should throw an error if it fails
- ob = self.GetxForm().evaluate_input("Name")
- if self.inputs["Relative to"].is_linked:
- try:
- relative = self.GetxForm("Relative to").bGetObject().name
- except Exception as e: # same here
- prRed(f"Execution failed at {self}: no relative object found for morph target, despite link existing.")
- raise e
- vg = self.evaluate_input("Vertex Group") if self.evaluate_input("Vertex Group") else "" # just make sure it is a string
-
- mt={"object":ob, "vertex_group":vg, "relative_shape":relative}
- self.parameters["Morph Target"] = mt
- self.parameters["Name"] = ob # this is redundant but it's OK since accessing the mt is tedious
- self.executed = True
- class DeformerMorphTargetDeform(MantisDeformerNode):
- '''A node representing an armature deformer'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Deformer",
- "Use Shape Key",
- "Use Offset",
- ]
- outputs = [
- "Deformer",
- ]
- self.outputs.init_sockets(outputs)
- self.inputs.init_sockets(inputs)
- self.init_parameters(additional_parameters={"Name":None})
- self.set_traverse([("Deformer", "Deformer")])
- self.node_type = "LINK"
- self.prepared = True
- self.executed = True
- setup_custom_props(self)
-
- # bpy.data.node_groups["Morph Deform.045"].nodes["Named Attribute.020"].data_type = 'FLOAT_VECTOR'
- # bpy.context.object.add_rest_position_attribute = True
- def reset_execution(self):
- return super().reset_execution()
- self.executed=True
- def gen_morph_target_modifier(self, xf, context):
- # first let's see if this is a no-op
- targets = []
- for k,v in self.inputs.items():
- if "Target" in k:
- targets.append(v)
- if not targets:
- return # nothing to do here.
-
- # at this point we make the node tree
- from .geometry_node_graphgen import gen_morph_target_nodes
- m, props_sockets = gen_morph_target_nodes(
- self.evaluate_input("Name"),
- xf.bGetObject(),
- targets,
- context,
- use_offset=self.evaluate_input("Use Offset"))
- self.bObject.append(m)
- evaluate_sockets(self, m, props_sockets)
- finish_drivers(self)
- def gen_shape_key_lattice(self, xf, context):
- # first check if we need to do anything
- targets = []
- for k,v in self.inputs.items():
- if "Target" in k:
- targets.append(v)
- if not targets:
- return # nothing to do here
- # TODO: deduplicate the code above here
- from time import time
- start_time = time()
- from bpy import data
- ob = xf.bGetObject()
- dg = context.view_layer.depsgraph
- dg.update()
- if xf.has_shape_keys == False:
- lat = ob.data.copy()
- ob.data = lat
- ob.add_rest_position_attribute = True
- ob.shape_key_clear()
- ob.shape_key_add(name='Basis', from_mix=False)
- else:
- lat = ob.data
- xf.has_shape_keys = True
-
- # first make a basis shape key
- keys, props_sockets, ={}, {}
- for i, t in enumerate(targets):
- mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
- if sk_ob is None:
- sk_ob = data.objects.new(mt_node.evaluate_input("Name"), data.meshes.new_from_object(ob))
- context.collection.objects.link(sk_ob)
- prOrange(f"WARN: no object found for f{mt_node}; creating duplicate of current object ")
- sk_ob = dg.id_eval_get(sk_ob)
- mt_name = sk_ob.name
- vg = mt_node.parameters["Morph Target"]["vertex_group"]
- if vg: mt_name = mt_name+"."+vg
-
- sk = ob.shape_key_add(name=mt_name, from_mix=False)
- # the shapekey data is absolute point data for each vertex, in order, very simple
- # SERIOUSLY IMPORTANT:
- # use the current position of the vertex AFTER SHAPE KEYS AND DEFORMERS
- # easiest way to do it is to eval the depsgraph
- # TODO: try and get it without depsgraph update, since that may be (very) slow
- sk_m = sk_ob.data#data.meshes.new_from_object(sk_ob, preserve_all_data_layers=True, depsgraph=dg)
- for j in range(len(m.vertices)):
- sk.data[j].co = sk_m.vertices[j].co # assume they match
- # data.meshes.remove(sk_m)
- sk.vertex_group = vg
- sk.slider_min = -10
- sk.slider_max = 10
- keys[mt_name]=sk
- props_sockets[mt_name]= ("Value."+str(i).zfill(3), 1.0)
- for i, t in enumerate(targets):
- mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
- if sk_ob is None: continue
- if rel := mt_node.parameters["Morph Target"]["relative_shape"]:
- sk = keys.get(mt_name)
- sk.relative_key = keys.get(rel)
-
- self.bObject.append(sk.id_data)
- evaluate_sockets(self, sk.id_data, props_sockets)
- finish_drivers(self)
- prWhite(f"Initializing morph target took {time() -start_time} seconds")
-
- def gen_shape_key(self, xf, context):
- # TODO: make this a feature of the node definition that appears only when there are no prior deformers - and shows a warning!
- # TODO: the below works well, but it is quite slow. It does not seem to have better performence. Its only advantage is export to FBX.
- # there are a number of things I need to fix here
- # - reuse shape keys if possible
- # - figure out how to make this a lot faster
- # - edit the xForm stuff to delete drivers from shape key ID's, since they belong to the Key, not the Object.
- # first check if we need to do anythign
- targets = []
- for k,v in self.inputs.items():
- if "Target" in k:
- targets.append(v)
- if not targets:
- return # nothing to do here
- from time import time
- start_time = time()
- from bpy import data
- ob = xf.bGetObject()
- dg = context.view_layer.depsgraph
- dg.update()
- if xf.has_shape_keys == False:
- match ob.type:
- case 'MESH':
- ob_data = data.meshes.new_from_object(ob, preserve_all_data_layers=True, depsgraph=dg)
- case 'LATTICE':
- ob_data = ob.data.copy()
- ob.data = ob_data
- ob.add_rest_position_attribute = True
- ob.shape_key_clear()
- ob.shape_key_add(name='Basis', from_mix=False)
- else:
- ob_data = ob.data
- xf.has_shape_keys = True
-
- # using the built-in shapekey feature is actually a lot harder in terms of programming because I need...
- # min/max, as it is just not a feature of the GN version
- # to carry info from the morph target node regarding relative shapes and vertex groups and all that
- # the drivers may be more difficult to apply, too.
- # hafta make new geometry for the object and add shape keys and all that
- # the benefit to all this being exporting to game engines via .fbx
- # first make a basis shape key
- keys={}
- props_sockets={}
- for i, t in enumerate(targets):
- mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
- if sk_ob is None:
- sk_ob = data.objects.new(mt_node.evaluate_input("Name"), data.meshes.new_from_object(ob))
- context.collection.objects.link(sk_ob)
- prOrange(f"WARN: no object found for f{mt_node}; creating duplicate of current object ")
- sk_ob = dg.id_eval_get(sk_ob)
- mt_name = sk_ob.name
- vg = mt_node.parameters["Morph Target"]["vertex_group"]
- if vg: mt_name = mt_name+"."+vg
-
- sk = ob.shape_key_add(name=mt_name, from_mix=False)
- # the shapekey data is absolute point data for each vertex, in order, very simple
- # SERIOUSLY IMPORTANT:
- # use the current position of the vertex AFTER SHAPE KEYS AND DEFORMERS
- # easiest way to do it is to eval the depsgraph
- # TODO: try and get it without depsgraph update, since that may be (very) slow
- sk_m = sk_ob.data#data.meshes.new_from_object(sk_ob, preserve_all_data_layers=True, depsgraph=dg)
- match ob.type:
- case 'MESH':
- for j in range(len(ob_data.vertices)):
- sk.data[j].co = sk_m.vertices[j].co # assume they match
- case 'LATTICE':
- for j in range(len(ob.data.points)):
- sk.data[j].co = sk_m.points[j].co_deform
- # data.meshes.remove(sk_m)
- sk.vertex_group = vg
- sk.slider_min = -10
- sk.slider_max = 10
- keys[mt_name]=sk
- props_sockets[mt_name]= ("Value."+str(i).zfill(3), 1.0)
- for i, t in enumerate(targets):
- mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
- if sk_ob is None: continue
- if rel := mt_node.parameters["Morph Target"]["relative_shape"]:
- sk = keys.get(mt_name)
- sk.relative_key = keys.get(rel)
-
- self.bObject.append(sk.id_data)
- evaluate_sockets(self, sk.id_data, props_sockets)
- finish_drivers(self)
- prWhite(f"Initializing morph target took {time() -start_time} seconds")
- def bFinalize(self, bContext=None):
- prGreen(f"Executing Morph Deform node {self}")
- use_shape_keys = self.evaluate_input("Use Shape Key")
- # if there is a not a prior deformer then there should be an option to use plain 'ol shape keys
- # GN is always desirable as an option though because it can be baked & many other reasons
- if use_shape_keys: # check and see if we can.
- if self.inputs.get("Deformer"): # I guess this isn't available in some node group contexts... bad. FIXME
- if (links := self.inputs["Deformer"].links):
- if not links[0].from_node.parameters.get("Use Shape Key"):
- use_shape_keys = False
- elif links[0].from_node.parameters.get("Use Shape Key") == False:
- use_shape_keys = False
- self.parameters["Use Shape Key"] = use_shape_keys
- for xf in self.GetxForm():
- # Lattice objects do not support geometry nodes at this time.
- ob = xf.bGetObject()
- if ob and ob.type == 'LATTICE':
- if not use_shape_keys:
- raise NotImplementedError("Blender does not support Geometry Nodes for Lattices. "
- "Enable 'Shape Key' and execute again.")
- self.gen_shape_key(xf, bContext)
- elif use_shape_keys:
- self.gen_shape_key(xf, bContext)
- else:
- self.gen_morph_target_modifier(xf, bContext)
- class DeformerSurfaceDeform(MantisDeformerNode):
- '''A node representing an surface deform modifier'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, SurfaceDeformSockets)
- # now set up the traverse target...
- self.init_parameters(additional_parameters={"Name":None})
- self.set_traverse([("Deformer", "Deformer")])
- self.prepared = True
- def GetxForm(self, socket="Deformer"):
- if socket == "Deformer":
- return super().GetxForm()
- else:
- trace_xForm_back(self, socket)
-
- def bRelationshipPass(self, bContext = None,):
- self.executed = True
-
- def bFinalize(self, bContext=None):
- prGreen("Executing Surface Deform Node")
- mod_name = self.evaluate_input("Name")
- for xf in self.GetxForm():
- ob = xf.bGetObject()
- d = ob.modifiers.new(mod_name, type='SURFACE_DEFORM')
- if d is None:
- raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
- self.bObject.append(d)
- self.get_target_and_subtarget(d, input_name="Target")
- props_sockets = self.gen_property_socket_map()
- evaluate_sockets(self, d, props_sockets)
- def bModifierApply(self, bContext=None):
- from bpy import ops
- standard_modifier_bind(self, bContext, ops.object.surfacedeform_bind)
- class DeformerMeshDeform(MantisDeformerNode):
- '''A node representing a mesh deform modifier'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, MeshDeformSockets)
- # now set up the traverse target...
- self.init_parameters(additional_parameters={"Name":None})
- self.set_traverse([("Deformer", "Deformer")])
- self.prepared = True
- def GetxForm(self, socket="Deformer"):
- if socket == "Deformer":
- return super().GetxForm()
- else:
- trace_xForm_back(self, socket)
-
- def bRelationshipPass(self, bContext = None,):
- self.executed = True
-
- def bFinalize(self, bContext=None):
- prGreen("Executing Mesh Deform Node")
- mod_name = self.evaluate_input("Name")
- for xf in self.GetxForm():
- ob = xf.bGetObject()
- d = ob.modifiers.new(mod_name, type='MESH_DEFORM')
- if d is None:
- raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
- self.bObject.append(d)
- self.get_target_and_subtarget(d, input_name="Object")
- props_sockets = self.gen_property_socket_map()
- evaluate_sockets(self, d, props_sockets)
- def bModifierApply(self, bContext=None):
- from bpy import ops
- standard_modifier_bind(self, bContext, ops.object.meshdeform_bind)
- class DeformerLatticeDeform(MantisDeformerNode):
- '''A node representing a lattice deform modifier'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, LatticeDeformSockets)
- # now set up the traverse target...
- self.init_parameters(additional_parameters={"Name":None})
- self.set_traverse([("Deformer", "Deformer")])
- self.prepared = True
- def GetxForm(self, socket="Deformer"):
- if socket == "Deformer":
- return super().GetxForm()
- else:
- trace_xForm_back(self, socket)
-
- def bRelationshipPass(self, bContext = None,):
- self.executed = True
-
- def bFinalize(self, bContext=None):
- prGreen("Executing Lattice Deform Node")
- mod_name = self.evaluate_input("Name")
- for xf in self.GetxForm():
- ob = xf.bGetObject()
- d = ob.modifiers.new(mod_name, type='LATTICE')
- if d is None:
- raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
- self.bObject.append(d)
- self.get_target_and_subtarget(d, input_name="Object")
- props_sockets = self.gen_property_socket_map()
- evaluate_sockets(self, d, props_sockets)
- class DeformerSmoothCorrectiveDeform(MantisDeformerNode):
- '''A node representing a corrective smooth deform modifier'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, SmoothDeformSockets)
- # now set up the traverse target...
- self.init_parameters(additional_parameters={"Name":None})
- self.set_traverse([("Deformer", "Deformer")])
- self.prepared = True
- def GetxForm(self, socket="Deformer"):
- if socket == "Deformer":
- return super().GetxForm()
- else:
- trace_xForm_back(self, socket)
-
- def bRelationshipPass(self, bContext = None,):
- self.executed = True
-
- def bFinalize(self, bContext=None):
- prGreen("Executing Smooth Deform Node")
- mod_name = self.evaluate_input("Name")
- for xf in self.GetxForm():
- ob = xf.bGetObject()
- d = ob.modifiers.new(mod_name, type='CORRECTIVE_SMOOTH')
- if d is None:
- raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
- self.bObject.append(d)
- # self.get_target_and_subtarget(d, input_name="Object")
- props_sockets = self.gen_property_socket_map()
- evaluate_sockets(self, d, props_sockets)
|