Nodevember Tool: Copy/Paste Nodes anywhere, Solidify node

Hey everyone!

I don’t have cool artwork for nodevember, but I just released the Copy/Paste Nodes add-on on the extensions platform. The main goal is, to allow node trees to be shared in text form, without rebuilding from screenshots. I am looking for feedback, and testers! Let me know if you find this useful or if you have a node tree where it doesn’t work. It should work in all blender node trees (geometry, shaders, compositing, etc).

As an example, here is a geometry nodes version of the Solidify modifier. You can copy this text block, and paste in a geometry nodes tree with CTRL-ALT-V when the extension is installed.


# Copy/Paste Nodes JSON export (https://extensions.blender.org/add-ons/copy-paste-nodes/)
{
  "version": 1,
  "type": "GEOMETRY",
  "nodes": [
    {
      "bl_idname": "GeometryNodeGroup",
      "node_tree": "Solidify",
      "props": {"_loc": [-60.0, 80.0], "name": "Solidify"},
      "inputs": {"0": {"name": "Mesh", "links": []}}
    }
  ],
  "node_trees": {
    ".High Quality Normal": {
      "nodes": [
        {
          "bl_idname": "NodeGroupOutput",
          "props": {"_loc": [1100.0, 100.0], "name": "Group Output"},
          "inputs": {"0": {"name": "Normal", "_val": [0.0, 0.0, 0.0], "links": [["Vector Math.003", 0]]}}
        },
        {
          "bl_idname": "GeometryNodeInputNormal",
          "props": {"_loc": [-220.0, 20.0], "name": "Normal.005"}
        },
        {
          "bl_idname": "GeometryNodeFieldOnDomain",
          "props": {
            "data_type": "FLOAT_VECTOR",
            "domain": "EDGE",
            "_loc": [740.0, 100.0],
            "name": "Evaluate on Domain"
          },
          "inputs": {"0": {"name": "Value", "_val": [0.0, 0.0, 0.0], "links": [["Switch", 0]]}}
        },
        {
          "bl_idname": "GeometryNodeFieldOnDomain",
          "props": {
            "data_type": "FLOAT_VECTOR",
            "domain": "FACE",
            "_loc": [-40.0, 20.0],
            "name": "Evaluate on Domain.001"
          },
          "inputs": {"0": {"name": "Value", "_val": [0.0, 0.0, 0.0], "links": [["Normal.005", 1]]}}
        },
        {
          "bl_idname": "GeometryNodeInputMeshEdgeAngle",
          "props": {"_loc": [-40.0, -140.0], "name": "Edge Angle"}
        },
        {
          "bl_idname": "ShaderNodeVectorMath",
          "props": {"_loc": [340.0, -60.0], "name": "Vector Math", "operation": "SCALE"},
          "inputs": {
            "0": {"name": "Vector", "links": [["Vector Math.001", 0]]},
            "3": {"name": "Scale", "links": [["Math", 0]]}
          }
        },
        {
          "bl_idname": "GeometryNodeInputMeshEdgeNeighbors",
          "props": {"_loc": [140.0, 140.0], "name": "Edge Neighbors"}
        },
        {
          "bl_idname": "FunctionNodeCompare",
          "props": {"data_type": "INT", "_loc": [340.0, 200.0], "name": "Compare.001", "operation": "EQUAL"},
          "inputs": {"2": {"name": "A", "links": [["Edge Neighbors", 0]]}, "3": {"name": "B", "_val": 2}}
        },
        {
          "bl_idname": "GeometryNodeSwitch",
          "props": {"input_type": "VECTOR", "_loc": [560.0, 100.0], "name": "Switch"},
          "inputs": {
            "0": {"name": "Switch", "links": [["Compare.001", 0]]},
            "1": {"name": "False", "_val": [0.0, 0.0, 0.0], "links": [["Vector Math.001", 0]]},
            "2": {"name": "True", "_val": [0.0, 0.0, 0.0], "links": [["Vector Math", 0]]}
          }
        },
        {
          "bl_idname": "ShaderNodeMath",
          "props": {"_loc": [140.0, -140.0], "name": "Math", "operation": "MULTIPLY"},
          "inputs": {
            "0": {"name": "Value", "links": [["Edge Angle", 0]]},
            "1": {"name": "Value", "_val": 2.0, "links": [["Math.002", 0]]}
          }
        },
        {
          "bl_idname": "ShaderNodeVectorMath",
          "props": {"_loc": [920.0, 100.0], "name": "Vector Math.003", "operation": "NORMALIZE"},
          "inputs": {"0": {"name": "Vector", "links": [["Evaluate on Domain", 0]]}}
        },
        {
          "bl_idname": "ShaderNodeVectorMath",
          "props": {"_loc": [140.0, 20.0], "name": "Vector Math.001", "operation": "NORMALIZE"},
          "inputs": {"0": {"name": "Vector", "links": [["Evaluate on Domain.001", 0]]}}
        },
        {
          "bl_idname": "ShaderNodeMath",
          "props": {"_loc": [-40.0, -240.0], "name": "Math.002", "operation": "DIVIDE"},
          "inputs": {"0": {"name": "Value", "_val": 2.0}, "1": {"name": "Value", "_val": 3.1415927}}
        }
      ],
      "interface": {
        "items_tree": [
          {
            "_val": [0.0, 0.0, 0.0],
            "dimensions": 3,
            "in_out": "OUTPUT",
            "name": "Normal",
            "socket_type": "NodeSocketVector"
          }
        ]
      },
      "props": {}
    },
    "Solidify": {
      "nodes": [
        {
          "bl_idname": "NodeGroupOutput",
          "props": {"_loc": [780.0, 60.0], "name": "Group Output"},
          "inputs": {
            "0": {"name": "Mesh", "links": [["Merge by Distance.001", 0]]},
            "1": {"name": "Inner", "_val": false, "links": [["Capture Attribute.001", 1]]},
            "2": {"name": "Outer", "_val": false, "links": [["Extrude Mesh", 1]]},
            "3": {"name": "Rim", "_val": false, "links": [["Extrude Mesh", 2]]}
          }
        },
        {
          "bl_idname": "NodeGroupInput",
          "props": {"_loc": [-1780.0, 160.0], "name": "Group Input"},
          "outputs": {"1": {"_val": 1.0}, "2": {"_val": -1.0}}
        },
        {
          "bl_idname": "GeometryNodeExtrudeMesh",
          "props": {"_loc": [-300.0, 20.0], "name": "Extrude Mesh"},
          "inputs": {
            "0": {"name": "Mesh", "links": [["Capture Attribute", 0]]},
            "3": {"name": "Offset Scale", "_val": 0.0},
            "4": {"name": "Individual", "_val": false}
          }
        },
        {
          "bl_idname": "FunctionNodeBooleanMath",
          "props": {"hide": true, "_loc": [20.0, 40.0], "name": "Boolean Math", "operation": "NOT"},
          "inputs": {"0": {"name": "Boolean", "links": [["Extrude Mesh", 1]]}}
        },
        {
          "bl_idname": "GeometryNodeJoinGeometry",
          "props": {"_loc": [20.0, 120.0], "name": "Join Geometry"},
          "inputs": {"0": {"name": "Geometry", "links": [["Capture Attribute.001", 0], ["Extrude Mesh", 0]]}}
        },
        {
          "bl_idname": "GeometryNodeFlipFaces",
          "props": {"_loc": [-340.0, 220.0], "name": "Flip Faces"},
          "inputs": {"0": {"name": "Mesh", "links": [["Capture Attribute", 0]]}}
        },
        {
          "bl_idname": "GeometryNodeCaptureAttribute",
          "props": {
            "capture_items": [
              {"data_type": "FLOAT", "name": "Thickness"},
              {"data_type": "BOOLEAN", "name": "Boundary"},
              {"data_type": "BOOLEAN", "name": "Even Thickness"},
              {"data_type": "FLOAT", "name": "Even Scale"},
              {"data_type": "FLOAT", "name": "Offset"}
            ],
            "_loc": [-523.5819, 116.82643],
            "name": "Capture Attribute"
          },
          "inputs": {
            "0": {"name": "Geometry", "links": [["Capture Attribute.002", 0]]},
            "1": {"name": "Thickness", "_val": 0.0, "links": [["Group Input", 1]]},
            "2": {"name": "Boundary", "_val": false, "links": [["Evaluate on Domain", 0]]},
            "3": {"name": "Even Thickness", "_val": false, "links": [["Group Input", 3]]},
            "4": {"name": "Even Scale", "_val": 0.0, "links": [["Evaluate on Domain.001", 0]]},
            "5": {"name": "Offset", "_val": 0.0, "links": [["Group Input", 2]]}
          }
        },
        {
          "bl_idname": "GeometryNodeSetPosition",
          "props": {"_loc": [420.0, 120.0], "name": "Set Position"},
          "inputs": {
            "0": {"name": "Geometry", "links": [["Join Geometry", 0]]},
            "3": {"name": "Offset", "links": [["Vector Math.002", 0]]}
          }
        },
        {
          "bl_idname": "ShaderNodeMath",
          "props": {"hide": true, "_loc": [-100.0, -100.0], "name": "Math"},
          "inputs": {
            "0": {"name": "Value", "links": [["Extrude Mesh", 1]]},
            "1": {"name": "Value", "_val": 1.0, "links": [["Math.001", 0]]}
          }
        },
        {
          "bl_idname": "ShaderNodeMath",
          "props": {"_loc": [-300.0, -300.0], "name": "Math.001", "operation": "MULTIPLY_ADD"},
          "inputs": {
            "0": {"name": "Value", "_val": -1.0, "links": [["Capture Attribute", 5]]},
            "2": {"name": "Value", "_val": -0.5}
          }
        },
        {
          "bl_idname": "ShaderNodeVectorMath",
          "props": {"hide": true, "_loc": [240.0, 40.0], "name": "Vector Math.002", "operation": "SCALE"},
          "inputs": {
            "0": {"name": "Vector", "links": [["Capture Attribute.002", 1]]},
            "3": {"name": "Scale", "links": [["Math.005", 0]]}
          }
        },
        {
          "bl_idname": "GeometryNodeCaptureAttribute",
          "props": {
            "capture_items": [{"data_type": "BOOLEAN", "name": "Inner"}],
            "domain": "FACE",
            "_loc": [-160.0, 220.0],
            "name": "Capture Attribute.001"
          },
          "inputs": {
            "0": {"name": "Geometry", "links": [["Flip Faces", 0]]},
            "1": {"name": "Inner", "_val": true}
          }
        },
        {
          "bl_idname": "ShaderNodeVectorMath",
          "props": {
            "hide": true,
            "_loc": [-1240.0, -80.0],
            "name": "Vector Math.004",
            "operation": "DOT_PRODUCT"
          },
          "inputs": {
            "0": {"name": "Vector", "links": [["Capture Attribute.002", 1]]},
            "1": {"name": "Vector", "links": [["Normal.003", 1]]}
          }
        },
        {
          "bl_idname": "ShaderNodeMath",
          "props": {"_loc": [-1060.0, -260.0], "name": "Math.004", "operation": "DIVIDE"},
          "inputs": {
            "0": {"name": "Value", "_val": 1.0},
            "1": {"name": "Value", "links": [["Vector Math.004", 1]]}
          }
        },
        {
          "bl_idname": "ShaderNodeMath",
          "props": {"hide": true, "_loc": [-100.0, -260.0], "name": "Math.002", "operation": "MULTIPLY"},
          "inputs": {
            "0": {"name": "Value", "links": [["Capture Attribute", 1]]},
            "1": {"name": "Value", "links": [["Capture Attribute", 4]]}
          }
        },
        {
          "bl_idname": "GeometryNodeSwitch",
          "props": {"input_type": "FLOAT", "_loc": [60.0, -140.0], "name": "Switch"},
          "inputs": {
            "0": {"name": "Switch", "links": [["Capture Attribute", 3]]},
            "1": {"name": "False", "_val": 0.0, "links": [["Capture Attribute", 1]]},
            "2": {"name": "True", "_val": 0.0, "links": [["Math.002", 0]]}
          }
        },
        {
          "bl_idname": "GeometryNodeFieldOnDomain",
          "props": {"domain": "CORNER", "_loc": [-720.0, -80.0], "name": "Evaluate on Domain.001"},
          "inputs": {"0": {"name": "Value", "links": [["Switch.002", 0]]}}
        },
        {
          "bl_idname": "ShaderNodeMath",
          "props": {"hide": true, "_loc": [-880.0, -260.0], "name": "Math.003", "operation": "ABSOLUTE"},
          "inputs": {"0": {"name": "Value", "links": [["Math.004", 0]]}}
        },
        {
          "bl_idname": "FunctionNodeBooleanMath",
          "props": {"hide": true, "_loc": [20.0, 0.0], "name": "Boolean Math.001"},
          "inputs": {
            "0": {"name": "Boolean", "links": [["Boolean Math", 0]]},
            "1": {"name": "Boolean", "links": [["Capture Attribute", 2]]}
          }
        },
        {
          "bl_idname": "GeometryNodeCaptureAttribute",
          "props": {
            "capture_items": [{"data_type": "FLOAT_VECTOR", "name": "Normal"}],
            "_loc": [-1420.0, 120.0],
            "name": "Capture Attribute.002"
          },
          "inputs": {
            "0": {"name": "Geometry", "links": [["Group Input", 0]]},
            "1": {"name": "Normal", "_val": [0.0, 0.0, 0.0], "links": [["Switch.001", 0]]}
          }
        },
        {
          "bl_idname": "GeometryNodeInputNormal",
          "props": {"_loc": [-1780.0, -120.0], "name": "Normal.003"}
        },
        {
          "bl_idname": "GeometryNodeGroup",
          "node_tree": ".High Quality Normal",
          "props": {"_loc": [-1780.0, -20.0], "name": "Group.001"}
        },
        {
          "bl_idname": "GeometryNodeSwitch",
          "props": {"input_type": "VECTOR", "_loc": [-1600.0, 40.0], "name": "Switch.001"},
          "inputs": {
            "0": {"name": "Switch", "links": [["Group Input", 4]]},
            "1": {"name": "False", "_val": [0.0, 0.0, 0.0], "links": [["Normal.003", 1]]},
            "2": {"name": "True", "_val": [0.0, 0.0, 0.0], "links": [["Group.001", 0]]}
          }
        },
        {
          "bl_idname": "ShaderNodeMath",
          "props": {"hide": true, "_loc": [60.0, -100.0], "name": "Math.005", "operation": "MULTIPLY"},
          "inputs": {
            "0": {"name": "Value", "links": [["Math", 0]]},
            "1": {"name": "Value", "links": [["Switch", 0]]}
          }
        },
        {
          "bl_idname": "FunctionNodeCompare",
          "props": {"_loc": [-1060.0, -80.0], "name": "Compare", "operation": "EQUAL"},
          "inputs": {
            "0": {"name": "A", "links": [["Vector Math.004", 1]]},
            "12": {"name": "Epsilon", "_val": 0.0001}
          }
        },
        {
          "bl_idname": "GeometryNodeSwitch",
          "props": {"input_type": "FLOAT", "_loc": [-880.0, -80.0], "name": "Switch.002"},
          "inputs": {
            "0": {"name": "Switch", "links": [["Compare", 0]]},
            "1": {"name": "False", "_val": 0.0, "links": [["Math.003", 0]]},
            "2": {"name": "True", "_val": 1.0}
          }
        },
        {
          "bl_idname": "GeometryNodeMergeByDistance",
          "props": {"_loc": [600.0, 120.0], "name": "Merge by Distance.001"},
          "inputs": {
            "0": {"name": "Geometry", "links": [["Set Position", 0]]},
            "1": {"name": "Selection", "links": [["Boolean Math.001", 0]]},
            "2": {"name": "Distance", "_val": 1e-05}
          }
        },
        {
          "bl_idname": "GeometryNodeInputMeshEdgeNeighbors",
          "props": {"_loc": [-1040.0, 220.0], "name": "Edge Neighbors"}
        },
        {
          "bl_idname": "FunctionNodeCompare",
          "props": {"data_type": "INT", "_loc": [-880.0, 220.0], "name": "Compare.001", "operation": "EQUAL"},
          "inputs": {"2": {"name": "A", "links": [["Edge Neighbors", 0]]}, "3": {"name": "B", "_val": 1}}
        },
        {
          "bl_idname": "GeometryNodeFieldOnDomain",
          "props": {
            "data_type": "BOOLEAN",
            "domain": "EDGE",
            "_loc": [-720.0, 220.0],
            "name": "Evaluate on Domain"
          },
          "inputs": {"0": {"name": "Value", "links": [["Compare.001", 0]]}}
        }
      ],
      "interface": {
        "items_tree": [
          {"in_out": "OUTPUT", "name": "Mesh", "socket_type": "NodeSocketGeometry"},
          {"in_out": "OUTPUT", "name": "Inner", "socket_type": "NodeSocketBool"},
          {"in_out": "OUTPUT", "name": "Outer", "socket_type": "NodeSocketBool"},
          {"in_out": "OUTPUT", "name": "Rim", "socket_type": "NodeSocketBool"},
          {"in_out": "INPUT", "name": "Mesh", "socket_type": "NodeSocketGeometry"},
          {
            "_val": 0.01,
            "in_out": "INPUT",
            "max_value": 10000.0,
            "min_value": -10000.0,
            "name": "Thickness"
          },
          {
            "_val": -1.0,
            "in_out": "INPUT",
            "max_value": 1.0,
            "min_value": -1.0,
            "name": "Offset",
            "subtype": "FACTOR"
          },
          {"in_out": "INPUT", "name": "Even Thickness", "socket_type": "NodeSocketBool"},
          {"in_out": "INPUT", "name": "High Quality Normals", "socket_type": "NodeSocketBool"}
        ]
      },
      "props": {"color_tag": "GEOMETRY", "is_modifier": true}
    }
  }
}
5 Likes

This looks really useful I can definitely think of lots of uses for it.

Hopefully lots of tutorials and videos start using it, that would be really useful. I can also see it might be good for support threads, since you can share the editable nodetree without sharing the whole file.

Great idea.

Will it handle paths to files in nodes like image texture or import .csv?

1 Like

For the “Import .csv” nodes and similar that just have a path, yes. For image textures it will only store the name of the image datablock, and will try to reconnect to the same datablock if it exists in the blend file where its pasted. I only serialize nodes, no other types at the moment, but images may be simple enough to be added.

Yes, and we have the string nodes for most cases so far too, minor.

Very nice, look forward to needing to use it in support questions here!

Im out of likes today, so Thank You.

1 Like