소스 검색

Add Object Instance Node

adds object instance node which uses another object as its data instead of geometry (with a Geometry Nodes node group).
also fixes morph deform not setting node group for modifier
Joseph Brandenburg 8 달 전
부모
커밋
4d8813a64f
4개의 변경된 파일184개의 추가작업 그리고 12개의 파일을 삭제
  1. 2 1
      __init__.py
  2. 18 1
      geometry_node_graphgen.py
  3. 139 10
      xForm_containers.py
  4. 25 0
      xForm_definitions.py

+ 2 - 1
__init__.py

@@ -97,9 +97,10 @@ link_relationship_category = [
     ]
 deformer_category=[NodeItem(cls.bl_idname) for cls in deformer_definitions.TellClasses()]
 xForm_category = [
-         NodeItem("xFormGeometryObject"),
+        NodeItem("xFormGeometryObject"),
         NodeItem("xFormBoneNode"),
         NodeItem("xFormArmatureNode"),
+        NodeItem("xFormObjectInstance"),
     ]
 driver_category = [
         NodeItem("LinkDrivenParameter"),

+ 18 - 1
geometry_node_graphgen.py

@@ -8,6 +8,7 @@ def gen_morph_target_nodes(mod_name, mod_ob, targets, context, use_offset=True):
     modifier = mod_ob.modifiers.new(mod_name, type='NODES')
     mod_ob.add_rest_position_attribute = True
     ng = data.node_groups.new(mod_name, "GeometryNodeTree")
+    modifier.node_group = ng
     ng.interface.new_socket("Geometry", in_out="INPUT", socket_type="NodeSocketGeometry")
     ng.interface.new_socket("Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry")
     inp = ng.nodes.new("NodeGroupInput")
@@ -108,4 +109,20 @@ def gen_morph_target_nodes(mod_name, mod_ob, targets, context, use_offset=True):
 
     for socket, ob in object_map.items():
         modifier[socket]=ob
-    return modifier, props_sockets
+    return modifier, props_sockets
+
+def gen_object_instance_node_group():
+    from bpy import data
+    ng = data.node_groups.new("Object Instance", "GeometryNodeTree")
+    ng.interface.new_socket("Object", in_out = "INPUT", socket_type="NodeSocketObject")
+    ng.interface.new_socket("As Instance", in_out = "INPUT", socket_type="NodeSocketBool")
+    ng.interface.new_socket("Object Instance", in_out="OUTPUT", socket_type="NodeSocketGeometry")
+    inp = ng.nodes.new("NodeGroupInput")
+    ob_node = ng.nodes.new("GeometryNodeObjectInfo")
+    out = ng.nodes.new("NodeGroupOutput")
+    ng.links.new(input=inp.outputs["Object"], output=ob_node.inputs["Object"])
+    ng.links.new(input=inp.outputs["As Instance"], output=ob_node.inputs["As Instance"])
+    ng.links.new(input=ob_node.outputs["Geometry"], output=out.inputs["Object Instance"])
+    inp.location = (-200, 0)
+    out.location = ( 200, 0)
+    return ng

+ 139 - 10
xForm_containers.py

@@ -9,12 +9,18 @@ def TellClasses():
              xFormArmature,
              xFormBone,
              xFormGeometryObject,
+             xFormObjectInstance,
            ]
 
 #*#-------------------------------#++#-------------------------------#*#
 # X - F O R M   N O D E S
 #*#-------------------------------#++#-------------------------------#*#
 
+def reset_object_data(ob):
+    # moving this to a common function so I can figure out the details later
+    ob.constraints.clear()
+    ob.animation_data_clear() # this is a little dangerous. TODO find a better solution since this can wipe animation the user wants to keep
+    ob.modifiers.clear() # I would also like a way to copy modifiers and their settings, or bake them down. oh well
 
 class xFormArmature:
     '''A node representing an armature object'''
@@ -732,9 +738,6 @@ class xFormBone:
 
 class xFormGeometryObject:
     '''A node representing an armature object'''
-
-    bObject = None
-
     def __init__(self, signature, base_tree):
         self.base_tree=base_tree
         self.signature = signature
@@ -824,18 +827,126 @@ class xFormGeometryObject:
             self.bObject = bpy.data.objects.new(self.evaluate_input("Name"), data)
         if self.bObject and (self.inputs["Geometry"].is_linked and self.bObject.type  in ["MESH", "CURVE"]):
             self.bObject.data = trace[-1].node.bGetObject()
-        # clear it
-        self.bObject.constraints.clear()
-        self.bObject.animation_data_clear() # this is a little dangerous. TODO find a better solution since this can wipe animation the user wants to keep
-        self.bObject.modifiers.clear() # I would also like a way to copy modifiers and their settings, or bake them down. oh well
-                    
+        
+        reset_object_data(self.bObject)
+        self.prepared = True
+
+    def bExecute(self, bContext = None,):
         try:
-            bpy.context.collection.objects.link(self.bObject)
+            bContext.collection.objects.link(self.bObject)
         except RuntimeError: #already in; but a dangerous thing to pass.
             pass
+        self.has_shape_keys = False
+        # putting this in bExecute simply prevents it from being run more than once.
+        # maybe I should do that with the rest of bPrepare, too.
+        props_sockets = {
+            'hide_viewport'    : ("Hide in Viewport", False),
+            'hide_render'      : ("Hide in Render", False),
+        }
+        evaluate_sockets(self, self.bObject, props_sockets)
+        self.executed = True
+
+    def bFinalize(self, bContext = None):
+        self.bSetParent()
+        matrix = self.evaluate_input("Matrix")
+        self.parameters['Matrix'] = matrix
+        self.bObject.matrix_world = matrix
+        for i, (driver_key, driver_item) in enumerate(self.drivers.items()):
+            print (wrapGreen(i), wrapWhite(self), wrapPurple(driver_key))
+            prOrange(driver_item)
+        finish_drivers(self)
+            
+        
+    def bGetObject(self, mode = 'POSE'):
+        return self.bObject
+
+
+class xFormObjectInstance:
+    """Represents an instance of an existing geometry object."""
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+          "Name"             : NodeSocket(is_input = True, name = "Name", node = self),
+          "Source Object"    : NodeSocket(is_input = True, name = "Source Object", node = self),
+          "As Instance"      : NodeSocket(is_input = True, name = "As Instance", node = self),
+          "Matrix"           : NodeSocket(is_input = True, name = "Matrix", node = self),
+          "Relationship"     : NodeSocket(is_input = True, name = "Relationship", node = self),
+          "Deformer"         : NodeSocket(is_input = True, name = "Relationship", node = self),
+          "Hide in Viewport" : NodeSocket(is_input = True, name = "Hide in Viewport", node = self),
+          "Hide in Render"   : NodeSocket(is_input = True, name = "Hide in Render", node = self),
+        }
+        self.outputs = {
+          "xForm Out" : NodeSocket(is_input = False, name="xForm Out", node = self), }
+        self.parameters = {
+          "Name":None, 
+          "Source Object":None, 
+          "As Instance": None,
+          "Matrix":None, 
+          "Relationship":None, 
+          "Deformer":None,
+          "Hide in Viewport":None,
+          "Hide in Render":None,
+        }
+        self.links = {} # leave this empty for now!
+        # now set up the traverse target...
+        self.inputs["Relationship"].set_traverse_target(self.outputs["xForm Out"])
+        self.outputs["xForm Out"].set_traverse_target(self.inputs["Relationship"])
+        self.node_type = "XFORM"
+        self.bObject = None
+        self.prepared, self.executed = False, False
+        self.has_shape_keys = False # Shape Keys will make a dupe so this is OK
+        self.drivers = {}
+
+    def bSetParent(self):
+        from bpy.types import Object
+        parent_nc = get_parent(self, type='LINK')
+        if (parent_nc):
+            parent = None
+            if self.inputs["Relationship"].is_linked:
+                trace = trace_single_line(self, "Relationship")
+                for node in trace[0]:
+                    if node is self: continue # lol
+                    if (node.node_type == 'XFORM'):
+                        parent = node; break
+                if parent is None:
+                    prWhite(f"INFO: no parent set for {self}.")
+                    return
+                
+                if (parent_object := parent.bGetObject()) is None:
+                    raise GraphError(f"Could not get parent object from node {parent} for {self}")
+                if isinstance(parent, xFormBone):
+                    armOb= parent.bGetParentArmature()
+                    self.bObject.parent = armOb
+                    self.bObject.parent_type = 'BONE'
+                    self.bObject.parent_bone = parent.bObject
+                    # self.bObject.matrix_parent_inverse = parent.parameters["Matrix"].inverted()
+                elif isinstance(parent_object, Object):
+                    self.bObject.parent = parent.bGetObject()
+
+    def bPrepare(self, bContext = None,):
+        from bpy import data
+        empty_mesh = data.meshes.get("MANTIS_EMPTY_MESH")
+        if not empty_mesh:
+            empty_mesh = data.meshes.new("MANTIS_EMPTY_MESH")
+        if not self.evaluate_input("Name"):
+            self.prepared = True
+            self.executed = True
+            # and return an error if there are any dependencies:
+            if self.hierarchy_connections:
+                raise GraphError(wrapRed(f"Cannot Generate object {self} because the chosen name is empty or invalid."))
+            return
+        self.bObject = data.objects.get(self.evaluate_input("Name"))
+        if (not self.bObject):
+                self.bObject = data.objects.new(self.evaluate_input("Name"), empty_mesh)
+        reset_object_data(self.bObject)
         self.prepared = True
 
     def bExecute(self, bContext = None,):
+        try:
+            bContext.collection.objects.link(self.bObject)
+        except RuntimeError: #already in; but a dangerous thing to pass.
+            pass
         self.has_shape_keys = False
         # putting this in bExecute simply prevents it from being run more than once.
         # maybe I should do that with the rest of bPrepare, too.
@@ -847,8 +958,23 @@ class xFormGeometryObject:
         self.executed = True
 
     def bFinalize(self, bContext = None):
+        # now we need to set the object instance up.
+        from bpy import data
+        trace = trace_single_line(self, "Source Object")
+        for node in trace[0]:
+            if node is self: continue # lol
+            if (node.node_type == 'XFORM'):
+                source_ob = node.bGetObject(); break
+        modifier = self.bObject.modifiers.new("Object Instance", type='NODES')
+        ng = data.node_groups.get("Object Instance")
+        if ng is None:
+            from .geometry_node_graphgen import gen_object_instance_node_group
+            ng = gen_object_instance_node_group()
+        modifier.node_group = ng
+        modifier["Socket_0"] = source_ob
+        modifier["Socket_1"] = self.evaluate_input("As Instance")
         self.bSetParent()
-        matrix = self.evaluate_input("Matrix")
+        matrix = self.evaluate_input("Matrix") # has to be done after parenting
         self.parameters['Matrix'] = matrix
         self.bObject.matrix_world = matrix
         for i, (driver_key, driver_item) in enumerate(self.drivers.items()):
@@ -861,5 +987,8 @@ class xFormGeometryObject:
         return self.bObject
 
 
+
+
+
 for c in TellClasses():
     setup_container(c)

+ 25 - 0
xForm_definitions.py

@@ -14,6 +14,7 @@ def TellClasses():
         xFormBoneNode,
         xFormArmatureNode,
         xFormGeometryObjectNode,
+        xFormObjectInstance,
         ]
 
 def default_traverse(self, socket):
@@ -341,3 +342,27 @@ class xFormGeometryObjectNode(Node, xFormNode):
         self.color = xFormColor
 
         self.initialized=True
+
+class xFormObjectInstance(Node, xFormNode):
+    """Represents an instance of an existing geometry object."""
+    bl_idname = "xFormObjectInstance"
+    bl_label = "Object Instance"
+    bl_icon = "EMPTY_AXIS"
+    initialized : bpy.props.BoolProperty(default = False)
+    
+    def init(self, context):
+        self.inputs.new('StringSocket', "Name")
+        self.inputs.new('xFormSocket', "Source Object")
+        self.inputs.new('BooleanSocket', "As Instance",)
+        self.inputs.new('MatrixSocket', "Matrix")
+        self.inputs.new('RelationshipSocket', "Relationship")
+        self.inputs.new('DeformerSocket', "Deformer")
+        self.inputs.new ("HideSocket", "Hide in Viewport")
+        self.inputs.new ("BooleanSocket", "Hide in Render")
+        self.outputs.new('xFormSocket', "xForm Out")
+
+        # color
+        self.use_custom_color = True
+        self.color = xFormColor
+
+        self.initialized=True